[projectreactor] 03/04: Imported Upstream version 1.1.6

Emmanuel Bourg ebourg-guest at moszumanska.debian.org
Fri May 27 07:01:50 UTC 2016


This is an automated email from the git hooks/post-receive script.

ebourg-guest pushed a commit to annotated tag debian/1.1.6-1
in repository projectreactor.

commit b55768c0eee9e6ee064a6eef1ecb08e301e40ea6
Author: Emmanuel Bourg <ebourg at apache.org>
Date:   Tue May 24 10:06:35 2016 +0200

    Imported Upstream version 1.1.6
---
 .gitignore                                         |   16 +
 CHANGELOG.md                                       |   56 +
 COMMITLOG.txt                                      |  304 ++++
 README.md                                          |   55 +
 build.gradle                                       |  374 +++++
 gradle.properties                                  |    1 +
 gradle/ide.gradle                                  |  109 ++
 gradle/setup.gradle                                |  128 ++
 .../main/java/reactor/alloc/AbstractReference.java |   70 +
 .../src/main/java/reactor/alloc/Allocator.java     |   39 +
 .../java/reactor/alloc/PartitionedAllocator.java   |   71 +
 .../src/main/java/reactor/alloc/Recyclable.java    |   16 +
 .../main/java/reactor/alloc/RecyclableNumber.java  |   51 +
 .../main/java/reactor/alloc/RecyclableString.java  |   24 +
 .../src/main/java/reactor/alloc/Reference.java     |   53 +
 .../reactor/alloc/ReferenceCountingAllocator.java  |  147 ++
 .../java/reactor/alloc/RingBufferAllocator.java    |  205 +++
 .../reactor/alloc/ThreadPartitionedAllocator.java  |   19 +
 .../alloc/factory/BatchFactorySupplier.java        |   90 ++
 .../alloc/factory/EventFactorySupplier.java        |   26 +
 .../main/java/reactor/alloc/factory/Factories.java |   50 +
 .../alloc/factory/NoArgConstructorFactory.java     |   38 +
 .../src/main/java/reactor/alloc/package-info.java  |    4 +
 .../alloc/spec/RingBufferAllocatorSpec.java        |  202 +++
 .../reactor/convert/ConversionFailedException.java |   58 +
 .../src/main/java/reactor/convert/Converter.java   |   46 +
 .../java/reactor/convert/DelegatingConverter.java  |   80 +
 .../java/reactor/convert/StandardConverters.java   |  197 +++
 .../main/java/reactor/convert/package-info.java    |    4 +
 .../src/main/java/reactor/core/Environment.java    |  361 +++++
 .../src/main/java/reactor/core/Observable.java     |  281 ++++
 .../src/main/java/reactor/core/Reactor.java        |  444 ++++++
 .../src/main/java/reactor/core/action/Action.java  |   92 ++
 .../main/java/reactor/core/action/ActionUtils.java |  147 ++
 .../main/java/reactor/core/action/BatchAction.java |  136 ++
 .../java/reactor/core/action/BufferAction.java     |   77 +
 .../java/reactor/core/action/CallbackAction.java   |   39 +
 .../reactor/core/action/CallbackEventAction.java   |   39 +
 .../java/reactor/core/action/CollectAction.java    |   62 +
 .../java/reactor/core/action/ConnectAction.java    |   40 +
 .../main/java/reactor/core/action/CountAction.java |   47 +
 .../java/reactor/core/action/DistinctAction.java   |   46 +
 .../java/reactor/core/action/FilterAction.java     |   72 +
 .../main/java/reactor/core/action/Flushable.java   |   35 +
 .../java/reactor/core/action/FlushableAction.java  |   47 +
 .../java/reactor/core/action/ForEachAction.java    |   77 +
 .../main/java/reactor/core/action/MapAction.java   |   39 +
 .../java/reactor/core/action/MapManyAction.java    |   59 +
 .../reactor/core/action/MovingWindowAction.java    |   90 ++
 .../main/java/reactor/core/action/Pipeline.java    |   46 +
 .../java/reactor/core/action/ReduceAction.java     |   69 +
 .../main/java/reactor/core/action/ScanAction.java  |   51 +
 .../java/reactor/core/action/SupplyAction.java     |   45 +
 .../java/reactor/core/action/TimeoutAction.java    |   58 +
 .../main/java/reactor/core/action/WhenAction.java  |   43 +
 .../java/reactor/core/action/WindowAction.java     |  104 ++
 .../java/reactor/core/composable/Composable.java   |  580 ++++++++
 .../java/reactor/core/composable/Deferred.java     |  139 ++
 .../main/java/reactor/core/composable/Promise.java |  620 ++++++++
 .../main/java/reactor/core/composable/Stream.java  |  745 ++++++++++
 .../java/reactor/core/composable/package-info.java |    5 +
 .../core/composable/spec/ComposableSpec.java       |   77 +
 .../core/composable/spec/DeferredPromiseSpec.java  |   55 +
 .../core/composable/spec/DeferredStreamSpec.java   |   77 +
 .../reactor/core/composable/spec/PromiseSpec.java  |   97 ++
 .../reactor/core/composable/spec/Promises.java     |  350 +++++
 .../reactor/core/composable/spec/StreamSpec.java   |   96 ++
 .../java/reactor/core/composable/spec/Streams.java |  188 +++
 .../reactor/core/composable/spec/package-info.java |    5 +
 .../core/configuration/ConfigurationReader.java    |   34 +
 .../configuration/DispatcherConfiguration.java     |   79 +
 .../reactor/core/configuration/DispatcherType.java |   56 +
 .../PropertiesConfigurationReader.java             |  203 +++
 .../core/configuration/ReactorConfiguration.java   |   75 +
 .../reactor/core/configuration/package-info.java   |    4 +
 .../java/reactor/core/dynamic/DynamicReactor.java  |   27 +
 .../core/dynamic/DynamicReactorFactory.java        |  338 +++++
 .../core/dynamic/annotation/Dispatcher.java        |   49 +
 .../reactor/core/dynamic/annotation/Notify.java    |   46 +
 .../java/reactor/core/dynamic/annotation/On.java   |   50 +
 .../core/dynamic/annotation/package-info.java      |    4 +
 .../java/reactor/core/dynamic/package-info.java    |    5 +
 .../reflect/MethodNotificationKeyResolver.java     |   33 +
 .../dynamic/reflect/MethodSelectorResolver.java    |   33 +
 .../SimpleMethodNotificationKeyResolver.java       |   53 +
 .../reflect/SimpleMethodSelectorResolver.java      |   55 +
 .../reactor/core/dynamic/reflect/package-info.java |    5 +
 .../dynamic/reflect/support/AnnotationUtils.java   |   55 +
 .../dynamic/reflect/support/MethodNameUtils.java   |   57 +
 .../core/dynamic/reflect/support/package-info.java |    4 +
 .../main/java/reactor/core/fork/ForkJoinPool.java  |  122 ++
 .../main/java/reactor/core/fork/ForkJoinTask.java  |  118 ++
 .../src/main/java/reactor/core/package-info.java   |    4 +
 .../java/reactor/core/processor/Operation.java     |   71 +
 .../java/reactor/core/processor/Processor.java     |  255 ++++
 .../java/reactor/core/processor/package-info.java  |    5 +
 .../reactor/core/processor/spec/ProcessorSpec.java |  173 +++
 .../reactor/core/processor/spec/package-info.java  |    5 +
 .../main/java/reactor/core/spec/ReactorSpec.java   |   34 +
 .../src/main/java/reactor/core/spec/Reactors.java  |  106 ++
 .../main/java/reactor/core/spec/package-info.java  |    5 +
 .../core/spec/support/DispatcherComponentSpec.java |  178 +++
 .../spec/support/EventRoutingComponentSpec.java    |  279 ++++
 .../reactor/core/spec/support/package-info.java    |    4 +
 .../core/support/DefaultEnvironmentSupplier.java   |   25 +
 .../java/reactor/core/support/NotifyConsumer.java  |   55 +
 .../src/main/java/reactor/event/Event.java         |  506 +++++++
 .../java/reactor/event/alloc/EventAllocator.java   |   84 ++
 .../dispatch/AbstractLifecycleDispatcher.java      |  224 +++
 .../dispatch/AbstractMultiThreadDispatcher.java    |   76 +
 .../dispatch/AbstractSingleThreadDispatcher.java   |   72 +
 .../reactor/event/dispatch/ActorDispatcher.java    |  118 ++
 .../java/reactor/event/dispatch/Dispatcher.java    |  104 ++
 .../event/dispatch/EventLoopDispatcher.java        |   63 +
 .../reactor/event/dispatch/ParkWaitStrategy.java   |   41 +
 .../event/dispatch/RingBufferDispatcher.java       |  171 +++
 .../event/dispatch/SynchronousDispatcher.java      |   87 ++
 .../dispatch/ThreadPoolExecutorDispatcher.java     |  156 ++
 .../dispatch/TraceableDelegatingDispatcher.java    |  101 ++
 .../event/dispatch/WorkQueueDispatcher.java        |  147 ++
 .../java/reactor/event/dispatch/package-info.java  |    5 +
 .../java/reactor/event/lifecycle/Pausable.java     |   46 +
 .../src/main/java/reactor/event/package-info.java  |    5 +
 .../event/registry/CachableRegistration.java       |  111 ++
 .../reactor/event/registry/CachingRegistry.java    |  164 +++
 .../java/reactor/event/registry/Registration.java  |  104 ++
 .../main/java/reactor/event/registry/Registry.java |   67 +
 .../java/reactor/event/registry/package-info.java  |    5 +
 .../routing/ArgumentConvertingConsumerInvoker.java |  197 +++
 .../routing/ConsumerFilteringEventRouter.java      |  132 ++
 .../reactor/event/routing/ConsumerInvoker.java     |   46 +
 .../java/reactor/event/routing/EventRouter.java    |   49 +
 .../routing/TraceableDelegatingEventRouter.java    |   37 +
 .../java/reactor/event/routing/package-info.java   |    5 +
 .../java/reactor/event/selector/ClassSelector.java |   57 +
 .../reactor/event/selector/HeaderResolver.java     |   39 +
 .../reactor/event/selector/MatchAllSelector.java   |   25 +
 .../reactor/event/selector/ObjectSelector.java     |   88 ++
 .../reactor/event/selector/PredicateSelector.java  |   33 +
 .../java/reactor/event/selector/RegexSelector.java |   94 ++
 .../main/java/reactor/event/selector/Selector.java |   52 +
 .../java/reactor/event/selector/Selectors.java     |  238 +++
 .../event/selector/SetMembershipSelector.java      |   38 +
 .../reactor/event/selector/UriPathSelector.java    |   81 ++
 .../reactor/event/selector/UriPathTemplate.java    |  147 ++
 .../java/reactor/event/selector/UriSelector.java   |  163 +++
 .../java/reactor/event/selector/package-info.java  |    4 +
 .../java/reactor/event/support/CallbackEvent.java  |   49 +
 .../java/reactor/event/support/EventConsumer.java  |   55 +
 .../main/java/reactor/filter/AbstractFilter.java   |   33 +
 .../src/main/java/reactor/filter/Filter.java       |   43 +
 .../src/main/java/reactor/filter/FirstFilter.java  |   38 +
 .../java/reactor/filter/PassThroughFilter.java     |   34 +
 .../src/main/java/reactor/filter/RandomFilter.java |   41 +
 .../main/java/reactor/filter/RoundRobinFilter.java |   82 ++
 .../reactor/filter/TraceableDelegatingFilter.java  |   36 +
 .../src/main/java/reactor/filter/package-info.java |    4 +
 .../src/main/java/reactor/function/Consumer.java   |   36 +
 .../src/main/java/reactor/function/Fn.java         |   28 +
 .../src/main/java/reactor/function/Function.java   |   40 +
 .../src/main/java/reactor/function/Functions.java  |  192 +++
 .../src/main/java/reactor/function/Predicate.java  |   39 +
 .../src/main/java/reactor/function/Predicates.java |  104 ++
 .../src/main/java/reactor/function/Supplier.java   |   36 +
 .../src/main/java/reactor/function/Suppliers.java  |  214 +++
 .../java/reactor/function/batch/BatchConsumer.java |   22 +
 .../java/reactor/function/batch/package-info.java  |    4 +
 .../main/java/reactor/function/package-info.java   |    7 +
 .../java/reactor/function/support/Boundary.java    |  119 ++
 .../function/support/CancelConsumerException.java  |   21 +
 .../function/support/DelegatingConsumer.java       |  210 +++
 .../main/java/reactor/function/support/Poller.java |  124 ++
 .../java/reactor/function/support/Resequencer.java |   94 ++
 .../function/support/SingleUseConsumer.java        |   90 ++
 .../main/java/reactor/function/support/Tap.java    |   60 +
 .../java/reactor/function/support/UriUtils.java    |  495 +++++++
 reactor-core/src/main/java/reactor/io/Buffer.java  | 1534 ++++++++++++++++++++
 .../src/main/java/reactor/io/BufferAllocator.java  |   66 +
 .../java/reactor/io/encoding/ByteArrayCodec.java   |   58 +
 .../src/main/java/reactor/io/encoding/Codec.java   |   53 +
 .../java/reactor/io/encoding/DelimitedCodec.java   |  134 ++
 .../src/main/java/reactor/io/encoding/Frame.java   |   26 +
 .../main/java/reactor/io/encoding/FrameCodec.java  |  155 ++
 .../io/encoding/JavaSerializationCodec.java        |   72 +
 .../java/reactor/io/encoding/LengthFieldCodec.java |  166 +++
 .../java/reactor/io/encoding/PassThroughCodec.java |   64 +
 .../reactor/io/encoding/SerializationCodec.java    |  143 ++
 .../java/reactor/io/encoding/StandardCodecs.java   |   49 +
 .../main/java/reactor/io/encoding/StringCodec.java |   86 ++
 .../io/encoding/compress/CompressionCodec.java     |   74 +
 .../reactor/io/encoding/compress/GzipCodec.java    |   31 +
 .../reactor/io/encoding/compress/SnappyCodec.java  |   31 +
 .../reactor/io/encoding/json/JacksonJsonCodec.java |   60 +
 .../java/reactor/io/encoding/json/JsonCodec.java   |  128 ++
 .../reactor/io/encoding/json/package-info.java     |    4 +
 .../java/reactor/io/encoding/kryo/KryoCodec.java   |   55 +
 .../java/reactor/io/encoding/package-info.java     |    4 +
 .../io/encoding/protobuf/ProtobufCodec.java        |   75 +
 .../src/main/java/reactor/io/package-info.java     |    4 +
 .../java/reactor/io/selector/JsonPathSelector.java |  295 ++++
 .../src/main/java/reactor/pool/LoadingPool.java    |   83 ++
 reactor-core/src/main/java/reactor/pool/Pool.java  |   47 +
 .../java/reactor/queue/BlockingQueueFactory.java   |   57 +
 .../java/reactor/queue/InMemoryQueuePersistor.java |  115 ++
 .../queue/IndexedChronicleQueuePersistor.java      |  264 ++++
 .../main/java/reactor/queue/PersistentQueue.java   |   82 ++
 .../main/java/reactor/queue/QueuePersistor.java    |   83 ++
 .../src/main/java/reactor/queue/package-info.java  |    5 +
 .../reactor/queue/spec/PersistentQueueSpec.java    |   88 ++
 .../main/java/reactor/queue/spec/package-info.java |    5 +
 .../main/java/reactor/support/Identifiable.java    |   12 +
 .../reactor/support/NamedDaemonThreadFactory.java  |   90 ++
 .../src/main/java/reactor/support/Supports.java    |   39 +
 .../main/java/reactor/timer/HashWheelTimer.java    |  490 +++++++
 .../java/reactor/timer/SimpleHashWheelTimer.java   |  196 +++
 .../src/main/java/reactor/timer/TimeUtils.java     |   43 +
 .../src/main/java/reactor/timer/Timer.java         |   98 ++
 .../src/main/java/reactor/tuple/Tuple.java         |  303 ++++
 .../src/main/java/reactor/tuple/Tuple1.java        |   44 +
 .../src/main/java/reactor/tuple/Tuple2.java        |   45 +
 .../src/main/java/reactor/tuple/Tuple3.java        |   46 +
 .../src/main/java/reactor/tuple/Tuple4.java        |   47 +
 .../src/main/java/reactor/tuple/Tuple5.java        |   48 +
 .../src/main/java/reactor/tuple/Tuple6.java        |   49 +
 .../src/main/java/reactor/tuple/Tuple7.java        |   50 +
 .../src/main/java/reactor/tuple/Tuple8.java        |   51 +
 .../src/main/java/reactor/tuple/TupleN.java        |   52 +
 .../src/main/java/reactor/tuple/package-info.java  |    4 +
 .../src/main/java/reactor/util/Assert.java         |  403 +++++
 .../main/java/reactor/util/CollectionUtils.java    |  530 +++++++
 .../src/main/java/reactor/util/IoUtils.java        |   51 +
 .../reactor/util/LinkedCaseInsensitiveMap.java     |  154 ++
 .../java/reactor/util/LinkedMultiValueMap.java     |  193 +++
 .../src/main/java/reactor/util/MultiValueMap.java  |   65 +
 .../src/main/java/reactor/util/ObjectUtils.java    |  995 +++++++++++++
 .../reactor/util/PartitionedReferencePile.java     |  134 ++
 .../src/main/java/reactor/util/StringUtils.java    | 1216 ++++++++++++++++
 .../src/main/java/reactor/util/TypeReference.java  |    9 +
 .../src/main/java/reactor/util/TypeUtils.java      |   22 +
 .../src/main/java/reactor/util/UUIDUtils.java      |  111 ++
 .../resources/META-INF/reactor/default.properties  |   55 +
 .../src/test/groovy/reactor/GroovyTestUtils.java   |  134 ++
 .../groovy/reactor/alloc/AllocatorsSpec.groovy     |  107 ++
 .../reactor/alloc/FactoryAllocatorSpec.groovy      |   41 +
 .../test/groovy/reactor/core/BoundarySpec.groovy   |   45 +
 .../groovy/reactor/core/EnvironmentSpec.groovy     |   45 +
 .../src/test/groovy/reactor/core/PollerSpec.groovy |   74 +
 .../test/groovy/reactor/core/ReactorsSpec.groovy   |  491 +++++++
 .../groovy/reactor/core/ResequencersSpec.groovy    |   35 +
 .../core/composable/spec/PromisesSpec.groovy       |  812 +++++++++++
 .../core/composable/spec/StreamsSpec.groovy        | 1104 ++++++++++++++
 .../PropertiesConfigurationReaderSpec.groovy       |  211 +++
 .../reactor/core/fork/ForkJoinPoolSpec.groovy      |   68 +
 .../reactor/core/processor/ProcessorsSpec.groovy   |  101 ++
 .../reactor/core/spec/ComponentSpecSpec.groovy     |   90 ++
 .../ConsumerFilteringEventRouterSpec.groovy        |  159 ++
 .../groovy/reactor/dispatch/DispatcherSpec.groovy  |  199 +++
 .../test/groovy/reactor/event/HeadersSpec.groovy   |  148 ++
 .../test/groovy/reactor/event/SelectorSpec.groovy  |  262 ++++
 .../groovy/reactor/filter/FirstFilterSpec.groovy   |   59 +
 .../reactor/filter/PassThroughFilterSpec.groovy    |   49 +
 .../groovy/reactor/filter/RandomFilterSpec.groovy  |   61 +
 .../reactor/filter/RoundRobinFilterSpec.groovy     |   98 ++
 .../src/test/groovy/reactor/io/BufferSpec.groovy   |  419 ++++++
 .../reactor/io/encoding/StandardCodecsSpec.groovy  |  126 ++
 .../encoding/compress/CompressionCodecsSpec.groovy |   52 +
 .../io/encoding/json/JacksonJsonCodecSpec.groovy   |   42 +
 .../reactor/io/encoding/json/JsonCodecSpec.groovy  |   41 +
 .../reactor/io/encoding/kryo/KryoCodecSpec.groovy  |   57 +
 .../io/encoding/protobuf/ProtobufCodecSpec.groovy  |   43 +
 .../reactor/io/encoding/protobuf/TestObjects.java  |  707 +++++++++
 .../io/encoding/protobuf/test_objects.proto        |    7 +
 .../reactor/queue/PersistentQueueSpec.groovy       |  118 ++
 .../groovy/reactor/queue/QueuePersistorSpec.groovy |   94 ++
 .../timer/HashWheelTimerBusySpinStrategy.groovy    |   70 +
 .../timer/HashWheelTimerSleepWaitStrategy.groovy   |   69 +
 .../timer/HashWheelTimerYieldingStrategy.groovy    |   72 +
 .../reactor/timer/SimpleHashWheelTimerSpec.groovy  |   70 +
 .../test/java/reactor/AbstractPerformanceTest.java |   84 ++
 .../src/test/java/reactor/AbstractReactorTest.java |   34 +
 .../test/java/reactor/alloc/AllocationTests.java   |   67 +
 .../java/reactor/alloc/EventAllocatorTests.java    |   24 +
 .../ConstructorParameterConverterTests.java        |   64 +
 .../src/test/java/reactor/core/AwaitTests.java     |   73 +
 .../test/java/reactor/core/EnvironmentTest.java    |   21 +
 .../reactor/core/composable/ComposableTests.java   |  458 ++++++
 .../core/dynamic/DynamicReactorFactoryTests.java   |   59 +
 .../test/java/reactor/core/dynamic/MyReactor.java  |   38 +
 .../java/reactor/core/fork/ForkJoinPoolTests.java  |   71 +
 .../core/processor/ProcessorThroughputTests.java   |  147 ++
 .../java/reactor/event/CachingAlgorithmTests.java  |  168 +++
 .../java/reactor/event/CachingRegistryTests.java   |  219 +++
 .../test/java/reactor/event/SelectorUnitTests.java |  190 +++
 .../function/support/DelegatingConsumerTest.java   |   63 +
 .../src/test/java/reactor/tuple/TupleTests.java    |   63 +
 .../src/test/java/reactor/util/AssertTests.java    |  176 +++
 .../java/reactor/util/CollectionUtilsTests.java    |  243 ++++
 .../util/LinkedCaseInsensitiveMapTests.java        |   58 +
 .../reactor/util/LinkedMultiValueMapTests.java     |   84 ++
 .../test/java/reactor/util/ObjectUtilsTests.java   |  575 ++++++++
 .../util/PartitionedReferencePileTests.java        |  136 ++
 .../test/java/reactor/util/StringUtilsTests.java   |  649 +++++++++
 .../src/test/java/reactor/util/UUIDUtilsTests.java |  250 ++++
 .../resources/META-INF/reactor/custom.properties   |   18 +
 .../META-INF/reactor/override-custom.properties    |   17 +
 .../META-INF/reactor/override-default.properties   |   17 +
 .../META-INF/reactor/unrecognized-type.properties  |   18 +
 reactor-core/src/test/resources/logback.xml        |   33 +
 .../reactor/groovy/ext/ComposableExtensions.groovy |  240 +++
 .../reactor/groovy/ext/ObservableExtensions.groovy |  147 ++
 .../reactor/groovy/ext/ProcessorExtensions.groovy  |   49 +
 .../groovy/ext/ReactorStaticExtensions.groovy      |   55 +
 .../reactor/groovy/support/ClosureConsumer.groovy  |   39 +
 .../groovy/support/ClosureEventConsumer.groovy     |   81 ++
 .../groovy/support/ClosureEventFunction.groovy     |   48 +
 .../reactor/groovy/support/ClosureFunction.groovy  |   40 +
 .../reactor/groovy/support/ClosurePredicate.groovy |   39 +
 .../reactor/groovy/support/ClosureReduce.groovy    |   45 +
 .../reactor/groovy/support/ClosureSupplier.groovy  |   39 +
 .../main/java/reactor/groovy/ext/package-info.java |    3 +
 .../groovy/support/ClosureTupleConsumer.java       |   26 +
 .../groovy/reactor/groovy/config/DSLUtils.groovy   |   27 +
 .../config/DispatcherConfigurationBuilder.groovy   |   30 +
 .../groovy/config/EnvironmentBuilder.groovy        |   68 +
 .../reactor/groovy/config/GroovyEnvironment.groovy |  185 +++
 .../reactor/groovy/config/ReactorBuilder.groovy    |  361 +++++
 .../groovy/config/ReactorScriptWrapper.groovy      |   21 +
 .../reactor/groovy/config/StreamEventRouter.java   |   90 ++
 .../org.codehaus.groovy.runtime.ExtensionModule    |   21 +
 .../groovy/reactor/groovy/CompileStaticTest.groovy |   55 +
 .../reactor/groovy/GroovyConfigurationSpec.groovy  |  132 ++
 .../reactor/groovy/GroovyPromisesSpec.groovy       |  173 +++
 .../groovy/reactor/groovy/GroovyReactorSpec.groovy |  164 +++
 .../groovy/reactor/groovy/GroovyStreamSpec.groovy  |  261 ++++
 .../reactor/groovy/StaticConfiguration.groovy      |  146 ++
 reactor-logback/README.md                          |   52 +
 .../main/java/reactor/logback/AsyncAppender.java   |  229 +++
 .../java/reactor/logback/DurableAsyncAppender.java |   72 +
 .../java/reactor/logback/DurableLogUtility.java    |  175 +++
 .../java/reactor/logback/LoggingEventRecord.java   |  166 +++
 .../src/test/java/reactor/dummy/Test.java          |   25 +
 .../java/reactor/logback/AsyncAppenderTests.java   |  138 ++
 reactor-logback/src/test/resources/logback.xml     |   72 +
 .../main/java/reactor/net/AbstractNetChannel.java  |  326 +++++
 .../src/main/java/reactor/net/AbstractNetPeer.java |  260 ++++
 .../src/main/java/reactor/net/NetChannel.java      |  171 +++
 .../src/main/java/reactor/net/NetClient.java       |   47 +
 .../src/main/java/reactor/net/NetServer.java       |   40 +
 .../src/main/java/reactor/net/Reconnect.java       |   29 +
 .../reactor/net/config/ClientSocketOptions.java    |   23 +
 .../reactor/net/config/CommonSocketOptions.java    |  165 +++
 .../reactor/net/config/ServerSocketOptions.java    |   70 +
 .../main/java/reactor/net/config/SslOptions.java   |  101 ++
 .../main/java/reactor/net/config/package-info.java |    4 +
 .../reactor/net/encoding/syslog/SyslogCodec.java   |  254 ++++
 .../reactor/net/encoding/syslog/SyslogMessage.java |  122 ++
 .../reactor/net/encoding/syslog/package-info.java  |    4 +
 .../net/netty/NettyClientSocketOptions.java        |   34 +
 .../net/netty/NettyEventLoopDispatcher.java        |   82 ++
 .../java/reactor/net/netty/NettyNetChannel.java    |  161 ++
 .../net/netty/NettyNetChannelInboundHandler.java   |  109 ++
 .../net/netty/NettyNetChannelOutboundHandler.java  |   28 +
 .../net/netty/NettyServerSocketOptions.java        |   36 +
 .../main/java/reactor/net/netty/package-info.java  |    6 +
 .../java/reactor/net/netty/tcp/NettyTcpClient.java |  390 +++++
 .../java/reactor/net/netty/tcp/NettyTcpServer.java |  213 +++
 .../reactor/net/netty/udp/NettyDatagramServer.java |  302 ++++
 .../main/java/reactor/net/spec/NetServerSpec.java  |  150 ++
 .../src/main/java/reactor/net/tcp/TcpClient.java   |  120 ++
 .../src/main/java/reactor/net/tcp/TcpServer.java   |  125 ++
 .../main/java/reactor/net/tcp/package-info.java    |    4 +
 .../tcp/spec/IncrementalBackoffReconnectSpec.java  |  159 ++
 .../java/reactor/net/tcp/spec/TcpClientSpec.java   |  189 +++
 .../main/java/reactor/net/tcp/spec/TcpClients.java |   58 +
 .../java/reactor/net/tcp/spec/TcpServerSpec.java   |   92 ++
 .../main/java/reactor/net/tcp/spec/TcpServers.java |   61 +
 .../java/reactor/net/tcp/spec/package-info.java    |    5 +
 .../reactor/net/tcp/ssl/SSLEngineSupplier.java     |   54 +
 .../java/reactor/net/tcp/ssl/package-info.java     |    4 +
 .../java/reactor/net/tcp/support/SocketUtils.java  |  378 +++++
 .../main/java/reactor/net/udp/DatagramServer.java  |  168 +++
 .../reactor/net/udp/spec/DatagramServerSpec.java   |   76 +
 .../java/reactor/net/udp/spec/DatagramServers.java |   56 +
 .../reactor/net/zmq/ZeroMQClientSocketOptions.java |   97 ++
 .../java/reactor/net/zmq/ZeroMQNetChannel.java     |  165 +++
 .../reactor/net/zmq/ZeroMQServerSocketOptions.java |   99 ++
 .../main/java/reactor/net/zmq/ZeroMQWorker.java    |  114 ++
 .../src/main/java/reactor/net/zmq/tcp/ZeroMQ.java  |  191 +++
 .../java/reactor/net/zmq/tcp/ZeroMQTcpClient.java  |  187 +++
 .../java/reactor/net/zmq/tcp/ZeroMQTcpServer.java  |  152 ++
 .../net/tcp/encoding/SyslogCodecSpec.groovy        |   26 +
 .../tcp/netty/ClientServerIntegrationSpec.groovy   |  162 +++
 .../net/tcp/netty/NettyTcpServerSpec.groovy        |  167 +++
 .../reactor/net/AbstractNetClientServerTest.java   |  259 ++++
 .../net/tcp/IncrementalBackoffReconnectTest.java   |   82 ++
 .../src/test/java/reactor/net/tcp/SpeedTests.java  |  164 +++
 .../test/java/reactor/net/tcp/TcpClientTests.java  |  591 ++++++++
 .../test/java/reactor/net/tcp/TcpServerTests.java  |  674 +++++++++
 .../reactor/net/tcp/ZeroMQClientServerTests.java   |  133 ++
 .../net/tcp/syslog/SyslogTcpServerTests.java       |  233 +++
 .../reactor/net/tcp/syslog/hdfs/HdfsConsumer.java  |   56 +
 .../java/reactor/net/tcp/syslog/test/Severity.java |   27 +
 .../reactor/net/tcp/syslog/test/SyslogCodec.java   |   62 +
 .../reactor/net/tcp/syslog/test/SyslogMessage.java |   87 ++
 .../net/tcp/syslog/test/SyslogMessageParser.java   |   74 +
 .../reactor/net/tcp/syslog/test/Timestamp.java     |   81 ++
 .../test/java/reactor/net/udp/UdpServerTests.java  |  174 +++
 .../resources/META-INF/reactor/default.properties  |   54 +
 reactor-net/src/test/resources/client.cer          |  Bin 0 -> 907 bytes
 reactor-net/src/test/resources/client.jks          |  Bin 0 -> 2262 bytes
 reactor-net/src/test/resources/server.cer          |  Bin 0 -> 907 bytes
 reactor-net/src/test/resources/server.jks          |  Bin 0 -> 2263 bytes
 settings.gradle                                    |    7 +
 src/api/overview.html                              |   17 +
 src/api/stylesheet.css                             |  640 ++++++++
 415 files changed, 50552 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4d92fcd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+.DS_Store
+bin
+build
+.classpath
+.eclipse
+.gradle
+.project
+.settings
+out
+*.log
+*.i*
+*.java.hsp
+deps.txt
+line_feed.txt
+*.index
+*.data
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..8d75c49
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,56 @@
+# Reactor 1.1.0 - CHANGELOG
+
+This is a non-exhaustive list of changes between Reactor 1.0 and 1.1:
+
+## `reactor-core`
+----
+
+* Numerous bug fixes and improvements
+
+### Stream / Promise
+
+* Improved Stream and Promise value-handling
+* Additional composition methods like connect(), merge(), timeout(), window() and more
+* Many methods moved into Composable so shared between Stream and Promise
+
+### Utilities
+
+* Robust HashWheelTimer implemenation based on a `RingBuffer`
+* Allocator API for efficient object pooling
+* New Consumer Registry implementation based on gs-collections 5.0 [1]
+
+### Testing
+
+* Numerous improvements to benchmarking
+* Added dedicated `reactor-benchmark` project based on JMH [2]
+* Removed most benchmarking code from core project
+* Expanded and improved test coverage
+
+## `reactor-logback`
+----
+
+* Extremely efficient high-speed logging using Java Chronicle
+* Re-written Reactor-based async appender implementations
+
+## `reactor-groovy`
+----
+
+* Better organization of Groovy support
+* AST-based extensions moved to their own subproject for better Gradle compatibility
+* Ready for Groovy 2.3 and Java 1.8
+
+## `reactor-net`
+----
+
+* Renamed `reactor-tcp` to `reactor-net`
+* Refactored base abstractions to handle both TCP and UDP
+* Added UDP support using Netty
+* Added ZeroMQ support using `jeromq`
+* Rewritten `reconnect` support 
+* Improved and exapanded testing
+* Numerous bug fixes and improvements
+
+----
+[1] - https://github.com/goldmansachs/gs-collections
+
+[2] - http://openjdk.java.net/projects/code-tools/jmh/
\ No newline at end of file
diff --git a/COMMITLOG.txt b/COMMITLOG.txt
new file mode 100644
index 0000000..fa5a85d
--- /dev/null
+++ b/COMMITLOG.txt
@@ -0,0 +1,304 @@
+b9f8687 Troubleshooting ZeroMQ Context termination, which randomly hangs on CI servers
+758e09e Add timeouts to ZeroMQ TCP tests
+47eb1f2 Added a test to ensure values that pass a Predicate in a Stream.filter expression are not passed to the else Composable.
+79a5f55 Simplifying TCP interaction tests
+3cbaf0b Increase timeout for CI servers
+66465bd Fixes #225 Moves the flush call into the event loop by adding a special handler just for doing writes. Now we write a Tuple of the data and a boolean of whether to do autoflush or not. The NetChannel.flush() is not implemented by writing a Tuple of `null` and `true` to force a flush.
+dba6efd Fix a failing test.
+66d4980 Change the way mapMany handles flushing. Add a test to make sure that data from returned Promises can be captured.
+de98461 Add a more meaningful toString impl.
+877da53 Lazily init HashWheelTimer since early instantiation at Environment init time leads to problems in STS where the object is reflectively instantiated but never cleaned up.
+d769f89 Troubleshooting random TCP errors that only seem to happen on build servers
+bc92ae6 Troubleshooting a strange error that only happens on build servers
+48bc2c2 Use explicit localhost IP rather than hostname
+be53c02 Trying to track down a not-reproducible error.
+aa7235e Create a dummy method for the AsyncAppenderTests
+4fdc9b3 Using 127.0.0.1 instead of the string "localhost" since it apparently means different things on different CI servers.
+b019861 Turn off logback appender loadtests.
+e6c5228 Tweaking ZeroMQ tests to work in different CI environments.
+6aa0381 Revert a previous change that returned a Boolean from the Promise returned when writing data to a channel. After some discussion, it became clear that this is a suboptimal interaction and that it would be better to either complete the Promise with null, which means success, or complete the Promise with an exception, which signals an error. This means in the ZeroMQ support an exception has to be created since it only returns true/false to indicate a success or failure on write.
+b395beb Update ZeroMQ support to provide inproc handling. Currently ZeroMQ support is broken as I try and find a reasonable way to get the shutdown/destroy/close to work without hanging. That's a very temporary situation however. I'm just pushing this now so I can link to it when I ask the experts how to fix it. :)
+7c2b206 Fix a compile error
+482d9bf Turn off serial warnings
+197e280 Reorganize ZeroMQ support source code. Add abstract test class and ZeroMQ helper for creating servers and clients.
+0aa0f75 Merge branch 'master' of github.com:reactor/reactor
+5008fc0 Default the TcpClient InetSocketAddress to accommodate using ZeroMQ style URI strings which are configured separately.
+0df7384 Merge pull request #325 from michaelklishin/issue323-set-selector
+4c738a6 SetSelector => SetMembershipSelector
+409eb67 Introduce Selectors#setMembership
+7c31aee Javadoc for SetSelector
+070bf13 Introduce a selector that matches on set membership
+6bc44c6 Tweaks to ZeroMQ support, start refactoring net tests to be more clean and reusable.
+5ff81eb Add Kryo to net tests.
+00fd6e2 Add options accessors.
+2a5e4c5 Add Iterable to NetClient and NetServer for accessing all connected channels
+e37d818 Fixes an issue with Buffer calling putInt on the underlying ByteBuffer when it should have called putShort.
+db55cf3 Merge pull request #322 from reactor/enhancement-247-zeromq
+199ae4c Fixes #247 Adds ZeroMQ client and server support.
+7e24333 Fixes a bug to pass true instead of null to onClose handler
+908f59b #247 Add getOptions() to TcpClient
+1d3469e When executing arbitrary Runnable tasks, the run() method wasn't actually being invoked inside the proper execution thread. This fixes that and adds a test to verify.
+b853f72 Open Event.Headers to any Object not restrict it to String,String
+83f0b80 Tweak the signature of sendAndReceive to allow for an Event type for a reply that's different than that for a request.
+76e5abf Merge pull request #318 from oiavorskyi/fix-get-dispatcher-exception
+2f9925f Fix for uninformative exception being thrown when no dispatcher is registered with particular name. Previously internal exception from AbstractFilter was propagated to clients making identifying of issue quite complicated.
+6e0f292 Switch to using channelInactive to detect a channel close event.
+c9d5b22 Arg. Generics and 1.6 vs 1.7. :P
+baa583f Make sure the previous chane is thread-safe.
+31b1dbc Add reference indirection and a removal of the registration from the internal list. Before it was only be removed from the cache when cancelled and not also the main list of registrations.
+52b993a Move mavenCentral() to the top of the repository list in the build.
+cb6236a Add a toString() on Event.Headers
+c8c099b Add a cancelAfterUse() call on connection reconnect.
+92b1729 Change SingleUseConsumer to use CAS operations instead of a synchronized block that was insufficient to prevent a race condition when multiple threads publish values at the same time.
+cdb586c Add a method to Promises to provide a single-use style Consumer by attaching a special Consumer that fulfills a Promise when the given Composable publishes the next value.
+2a9ea03 Update gradle wrapper.
+948969d Fixes #317 Changes NetChannel.close() return to Promise<Boolean>
+9252ad8 Minor tweak.
+35cee23 Only attempt log file cleanup if the directory exists. Otherwise an error occurs.
+b9bcf29 Merge pull request #316 from reactor/fix-306-processor-as-async
+c31187f Add a little javadoc.
+1450e8b Use Processor as async single-writer.
+0087c54 #306 Added log file deletion to beginning of test.
+57f6575 #306 Addresses issues with the AsyncAppender by re-implementing the underlying components.
+73eed61 Merge pull request #315 from reactor/better-tcpclient-reconnect
+8e02ee2 Tweaks to the way connections are opened and closed and how events are scheduled on the Reactor when those happen.
+909748b Normalize code formatting.
+8895fd1 Formatting tweaks and change to how close handlers are scheduled.
+700ace7 Formatting tweaks.
+08133e0 Force creation of a new Reactor on each new channel creation to ensure that consumers don't cause back references to hold onto dead channels and prevent their being GC'd.
+5941958 Integrated fixes from an issue with reactor-tcp-1.0.0.RELEASE that would leak connections if the connection got unexpectedly dropped and the cleanup/deregistration code didn't get a chance to run. The reconnection code has been rewritten to not rely on the connection registry but to use the reference embedded in the Netty ChannelInboundHandler which should render the references to that connection object unreachable when the channel itself goes out of scope.
+417dd2c Increase timeouts.
+2a94b9c switch back Reactor schedule extension
+a5b21bd Merge pull request #314 from reactor/enhancement-NoDefaultSelector-in-reactor
+c16c088 Fix leaked change from NoDefaultSelector branch
+827fd11 Remove default Selector and refactor Reactor.schedule to avoid needless allocation for a new Reactor (not optimum for flatMap / iterative Reactor creation)
+b453569 Fix race conditions on flush actions
+aca0a4f fix imports
+f65b976 Tweak the README to reflect recents tests using JMH benchmarks and the latest changes to CachingRegistry
+daecb8e Tweak CachingRegistry to provide a copy for iterators.
+07d5258 Ignore a test failure for the time being.
+386181f Merge pull request #313 from reactor/enhancement-consumer-registry
+1d1a7f5 Rewrite CachingRegistry to use the gs-collections rich collections library. Addresses #297 by adding a configuration knob for setting a "consumer not found" handler if no consumers were found for a given notification key.
+34c2409 Tweaks to event router implementations to get maximum efficiency.
+5020a48 Tweaks to ForEach action to use gs-collections for iteration, removed unused CTOR parameter.
+2e26b37 Add gs-collections rich collections library as a compile dependency.
+01dde04 Fixes #309 by merging PR #310. Adds a WaitStrategy configuration option.
+683ffd6 [processor] configure Disruptor's wait strategy #309
+fc48992 Update to Groovy version 2.2.2 which also meant having to tweak a couple constructor signatures since there seems to be a problem with 2.2.2 recognizing CTOR params if they use generics. Took off the generic part of the parameter and it works.
+135b04e Merging PR #307 Breaks out Groovy extensions into their own submodule so they can be compile separately (and first) to ensure they're available to other submodules. IDEA seems to have issues with them if this is not done in this order.
+fb7f075 Broke down reactor-groovy module to two separate ones so Intellij Idea could compile them. Issue was due to usage of extensions from same module. Added configuration to ide.gradle to add optional libs into Idea Provided scope making compilation from Idea possible.
+4137687 Merge pull request #305 from michaelklishin/master
+16f1009 Add Selectors.matchAll
+80feeaa Introduce MatchAllSelector, fixes #303
+a44cfdb Changed the NetServer start() and shutdown() methods to use a Promise<Boolean> rather than Promise<Void> so it's easier to distinguish between a timeout, which will return null, and a success, which will return true (otherwise the Promise will be in error and get() will throw an exception).
+71b88ea Add UDP server helper.
+26e587d Reorganize the NetServer specs so they share a common ancestry which makes it easier to configure from things like Spring where you're dealing with NetServer as an abstraction. Without this common ancestry, you have to create blocks of code that duplicate functionality of one or the other.
+04d13dc Merge pull request #300 from dharrigan/master
+676dc39 IncludeCallerData so that, for example, line numbers are passed along whenever a logging event occurs. To use, ensure that in your logback configuration you have something similar to:
+f067f45 Merge pull request #299 from reactor/enhancement-298-remoteAddress
+97592fb Fixes #298 Per request by @pidster reinstate the `remoteAddress()` function on `NetChannel`. Also added a test to ensure the information is populated.
+e188491 Remove reactor-benchmark and reactor-spring as that functionality has be moved into other artifacts.
+bb588a1 Clear compiler warnings.
+b575720 Merge pull request #292 from michaelklishin/patch-1
+4a98f75 Merge pull request #293 from michaelklishin/patch-2
+417a6af Fix a typo
+cd3966a Update README.md
+21f3de9 Merge branch 'master' of github.com:reactor/reactor
+44149c0 Fine grained error/flush cascading, mapMany cascade flushes to join on, enhance action utils to display flushable actions.
+d81351b Add Stream#counter(Stream<Long>> to count values until flush and feed the argument stream with each counter
+a1c53f0 #287 Ignore multicast tests because they are finicky on different systems.
+75fc031 #287 Refactored TCP support to use common abstractions called "NetXXX" and added UDP support.
+39c084d Merge pull request #291 from reactor/enhancement-284-merge-and-composable-fixes
+7428707 fix #284 add Composable#merge to connect N composable to the current pipeline explicit consume name from consume(Composable) to connectValues(Composable)  : values forwarded; to be inline with connect(Composable) : error and values forwarded. Add a promise.mapMany test Promote consumeFlush to Pipeline Fix promise flush cascading Remove Flushable generic type constraint
+262f7ce Merge pull request #289 from reactor/enhancement-288-composable-timeout
+93b195b fix #288 fix mapMany on groovy extention Change collectWithTimeout() to collect().timeout() "Various actions could benefit from a timeout, in fact even Promises will be interested in such action. A timeout action  triggers a parent flush (or itself if root), releasing any Flushable action (buffer, collect, propagate, reduce, window...).  Eventually, competing events from timeout flush and natural flush can be filtered out with a distinct() method to remove duplicate noise."
+ceb756f try to fix timed window tests by allocating an additional 200ms to sleeps (!) for the possible inconsistency of the time (hashwheeltimer) event loop period
+430117d complete promise signatures
+bf322d5 add Test, fix new filter() signature
+5307e75 few 	 * @since 1.1 add simple form filter() that evaluates incoming Boolean
+a935dc5 Added CHANGELOG.
+1a7f414 Formatting changes while code reviewing.
+7be7d0a remove EventBatcher since Stream.defer().batchSize(123).get().flushWhen{ predicate }.consumeFlush{ do stuff once }.consume{ do for each data }
+beeb805 Re align test with previous scenario to compare (1 reduce vs N reduce) Add Stream#reduce(function, supplier, batchSize) to arbitrarely select the batch size for a reduce
+ef78622 update gadle wrapper, update developers list
+269e3b6 is this the end ?
+8825f15 move things around to fix package tangles
+01ab73e move things around to fix package tangles
+9cdf78a Makes sure Stream#bufferConsumer listens for flush
+87f34ae Merge pull request #286 from reactor/enhancement-27-batchNotify
+ea8c2dd fix test
+4ad12ad Merge branch 'refs/heads/master' into enhancement-27-batchNotify
+bfa2ec0 100% test coverage for Stream Add Flushable interface for propagating flushes callbacks All stateful actions (reduce, collect, buffer, ..) are now Flushable Add collectWithTimeout, a size and time bound collection Add FlushableAction to consume flushes Add SupplyAction to notify an observable with a supplied value Add StreamSpec#generate(Supplier) to generate values on flush Add Stream#whenFlush(Predicate) to flush the Stream if predicate matches Add Composable#consumeFlush(Flush [...]
+936feae Tweaks to RingBufferAllocator batch allocation.
+ff1abf8 Add resolution getter to Timers.
+cd6d4d5 Upgrade TaskExecutor implementations to also implement ScheduledExecutorService for us as a Spring Integration TaskScheduler, which requires greater scheduling flexibility than just an Executor.
+b96e788 #279 Drop the default backlog values to 2048.
+9aa1ddf Fixes #281 Uses a nullSafeHashCode() function to accommodate null values in a Tuple.
+96b4485 Tweaks to suppress compiler warnings.
+53c0dcf This commit adds a Timer interface implemented by HashWheelTimer
+8fe53ea Put back List type and merge master
+3eb3834 Merge branch 'master' into enhancement-27-batchNotify
+4aa65a8 Move HashWheelTimer to its own package and extract a common interface.
+c67f3c3 Add serialVersionUID
+1c487f7 #273 Adds support for Spring AsyncTaskExecutors based on the RingBuffer and WorkQueue dispatcher implementations.
+e20a13a #273 Added ProducerType and WaitStrategy to the WorkQueueDispatcher config.
+7fe41dd Fix NettyEventLoopDispatcher to match new Dispatcher signature.
+866ed11 Fix bone-headed mistake. This is not the commit you are looking for. Move along.
+202fb14 Fix bone-headed mistake. This is not the commit you are looking for. Move along.
+6156722 Tweak to RingBufferDispatcher creation
+3d57475 Add throughput tests for Executor.execute methods on Dispatchers, drop values to reasonable settings so that low-powered CI servers don't choke on the build.
+a0a7987 Make Dispatcher extend Executor to turn each Dispatcher implementation into an Executor that can run arbitrary Runnable tasks.
+016a8f0 Bump up the default backlog values.
+619c092 try another package tangle luck
+4a249d6 Fix mapMany groovy Fix extension list Groovy Try to fix package Tangle
+929e513 all tests pass!
+b098f55 Deprecate EventLoopDispatcher Fix test Do not pool tasks for MultiThreadDispatcher (WorkQueue naturally pools from RingBuffer)
+1e83941 change test to scope WorkQueueDispatcher
+0d85709 Lock free multi thread dispatcher based on consistent hash routing
+68f0e21 Fix PropertiesConfig tests
+e3f3922 #271 Had to re-write the Dispatchers to not use the built-in Allocator implementations for the time being. The section of code covered by the Dispatchers is so critical to performance that there's virtually no leeway for errors or locking. We should take a look at how much garbage is created by the LinkedTransferQueue and replace that component in the multi-threaded Dispatchers.
+4555301 fix DispatcherSpec
+c522328 Fixed tests!!!!
+fc7f023 try to fix event loop logic
+5389ae0 fixed EventLoopDispatcher test
+52a2278 fix test
+010535f #271 Pushing the latest changes to attempt to fix the test inconsistency problem.
+a50e19c Merge branch 'refs/heads/master' into fix-271-recursive-dispatching
+fe936dc #271 Trying to troubleshoot odd dispatching problems that cause inconsistent test results.
+98a8a63 Improve test output to check against expected total result and verify that all events have been correctly dispatched
+a18ad25 Move TimeUtils in a dedicated package reactor.timer for package tangles.
+c79854c Move HashWheelTimer in a dedicated package reactor.timer for package tangles.
+e085bee Fix a generics problem
+37c8864 Merge pull request #269 from ifesdjeen/enchancement-232-event-factory
+e53879c Since a new version of HashWheelTimer is coming, comment this test out for the time being.
+e4d2541 Add Event Allocator
+37d9a38 Fix a failing test.
+085ba6a Trying to fix package tangle.
+ac12f6a Troubleshooting odd test failure that only occurs on drone.io CI server.
+4e8ea69 Playing an utterly pointless game of musical chairs to make Sonar happy.
+d120da2 Added serialVersionUID to TupleNs.
+31a49e8 Added serialVersionUID to Tuple.
+c88a273 Trying to fix a package tangle with the timer utils.
+13fb9f9 Reduce the number of tests for throughput testing so it doesn't fail on slower servers like CI servers.
+8d60b94 Merge pull request #267 from reactor/enhancement-232-allocator-recycler
+e68ee25 Update javadoc.
+d4fa5e0 Tweak throughput tests.
+bcd7b9b Be more specific on the maximum throughput verbiage.
+09707ed Remove the ReentrantLock and use a simplified synchronized block for updating the BitSet.
+c2845a8 Raise the backlog value for the EventLoopDispatcher throughput test. Turns out it doesn't really make that much difference.
+7ecb396 #232 Tweaking object allocation. Renamed FactoryAllocator to BatchFactorySupplier since it doesn't implement the Allocator interface. Left it in the alloc package since it has to do functionally with allocation.
+998d32a #232 Added FactoryAllocator for amortizing the cost of object creation by doing batch creations of objects and then doling those out one at a time until exhausted, at which point the pool is refilled, blocking the calling thread.
+10507a8 Tweak to fix failing test.
+1c5e55e Formatting tweaks.
+d272c7e #232 Fixes for allocator and dispatcher implementations built on top.
+adcf6e3 Add Serializable to Tuple
+5dc0a4c #232 Integrated Allocator into the Dispatchers.
+73d6eb1 #232 Adds generic object pooling based on reference counting.
+e04a351 Merge pull request #263 from reactor/enhancement-261-common-serializers
+340e8ae Added support for compression of data using either GZIP or Snappy.
+cd7bf25 Added support for Kryo, Protocol Buffers, and Jackson JSON support based around a shared SerializationCodec.
+5e15e94 Merge pull request #255 from reactor/enhancement-254-fixed-streams
+f7aa846 fix #254 Refactor Streams.defer(value) and Streams.defer(values) to returned a Stream rather than a Deferred, thus preventing fixed streams to be confused with Streams.defer()
+816f66f Merge pull request #252 from head-thrash/master
+92bf619 This presumably fixes Issue #248
+6575358 Merge pull request #251 from head-thrash/master
+65e2b5c Adds tests for Tcp Client and Tcp Server interactions:
+04af84c The exposesNettyByteBuf() test must use a SynchronousDispatcher since we're reading from the Netty ByteBuf directly. That must take place in Netty's IO event loop thread and not in some other thread, which causes random failures due to doing out-of-band IO in reading from Netty's buffer.
+f2475d4 Minor typo fix.
+76e193d #239 #248 Hopefully fixes the BufferOverflowException by growing the internal ByteBuffer by lengthFieldLength.
+2eeefea Merge pull request #246 from reactor/enhancement-241-error-handlers
+f6e30f9 Merge pull request #221 from kctang/master
+4f488d8 Minor test fix and Groovy version upgrade to 2.2.1
+0fd25f1 #241 #245 Adds a couple of constructor parameters to Reactor and a couple of methods to EventRoutingComponentSpec for specifying two types of error handlers and the Selector to be used as a default.
+aaf350d #241 The default exception handler should always be registered no matter the logging level.
+f602f44 Fixes #242 by notifying the Reactor on the class of the Exception rather than on the exception instance itself.
+e835e28 Merge pull request #240 from mingfai/master
+63107b3 Added a constructor with UncaughtExceptionHandler argument
+b67217d Added a ThreadPoolExecutorDispatcher constructor with Executor argument
+0a3001e Increase test timeout.
+b5ee68b Some doc on CachingRegistry
+d39bc0d Fix Prime Cache to target key Object and AnonymousKey instead of ObjectSelector selector
+4b44881 Fix Supplier exception handling
+285c908 Merge master and fix supplier dispatching on Promise
+4a1f845 fix test
+7d8d1ce fix tests and Dispatcher tail recursion issue
+afc2854 Remove @Before in ComposableThroughputTests as unused
+bd48fac Pick fixes for minor dispatcher issues from batchNotify branch
+290d3c7 Merge master and fix minor dispatcher issues
+53b9b48 Merge master and fix minor dispatcher issues
+2d98464 Fix #237 by adding a dedicated primeCache and update tests Minor compilation warning fix
+6a20bf1 Add batchNotify on Observable - ensure that 1 selection happens for a given iterable group of event. Events will be dispatched once and preselected consumers will be invoked. Add Composable.buffer to expose batchNotify (grouping events for a single dispatching) Rename Composable split() to batch() and update ForEachAction to use batchNotify Minor improvements over ArgumentConsumer that only takes 1 argument Minor improvements over ActionUtils to browse a buffered stream Use a Str [...]
+7b61078 Merge pull request #230 from reactor/optimized-registry
+f8604d8 Anonymous Selector doesn't need tuple2 Remove Selector anonymous constructor (prefer $()) Micro fixes
+5a24b07 minor fix
+fe901b4 Fix #228
+4091f0c Merge pull request #229 from reactor/optimized-registry
+c376c97 Fix TCP tests and reorder unregister connection
+c40e7b1 Fix implementation for Spring module
+d25aae1 Add registry#clear and fix TcpConnection#Close
+bc53d3e add test for ObjectSelector cacheMiss (test if prime caching is functional)
+fc98664 add test for cacheMiss on non 'direct' selector which are prime cached
+5ba9ed7 comment irrelevant test
+66fa5d4 revert excessive field delete
+f977443 put back Lifecycle interface checking
+0e284c4 FIx a bug in cache priming for ObjectSelector objects.
+e897e44 Update to use AtomicIntegerFieldUpdater and volatile fields for less overhead
+105557d fix import
+54678fc Fix failing tests.
+64049a7 #222 Fixes problems with excessive GC by re-implementing the CachingRegistry. Now uses arrays as much as possible and is an overall simpler design internally. Cancel no longer removes a Registration from the list. Instead a Registration is aware of it's cancelled status so refuses to match against any keys and returns null for an object when cancelled, rendering the Registration invisible. The cancelled Registration object is reclaimed when the array grows to accommodate new Regi [...]
+061ef65 Merge pull request #227 from ifesdjeen/feature/op/sliding-window
+dfc6dfe Add for the Moving Window
+829ccaf Add docstrings
+12b6182 Rename Sliding Window to Moving Window
+7a23320 Add an implementation of Sliding Window
+cdcafc8 Cover map many throughput
+4aa92e2 No need to do getObject on simple ObjectSelector
+41449cd remove selector uuid
+fbc23eb avoid double allocation for anonymous selector, remove useless tags field
+c992510 avoid double allocation for anonymous selector, remove useless tags field
+7bba671 Fixed window Test
+cb327af Merge pull request #226 from reactor/staging-improvements-composable
+ca9b8e3 Remove reference to deleted control() method.
+5cd226e revert Composable pause/cancel/resume on this branch and related control methods
+3aba1f5 new WindowAction on Stream, new Lifecycle defines pause, resume, cancel Registration, Composable and WindowAction now implement Lifecycle Stream now accept an environment to be used by some action (e.g. window for its timer)
+2ff2f2f Remove Linkable as non used anymore and confusing
+780bdac Add control(Observable) to Reactor Add pause/resume to Stream Add cancel to Composable Fix UriSelector matching Delete Linkable from Reactor
+c194b6c Change Tuple to internally use an Object array rather than a List to get around CPU time spent when Tuples are created in large quantities like in throughput tests.
+9e65671 Tweaked Selectors.uri() to try and be smart about what kind of Selector instance to return. Previously, only a UriPathSelector was returned. Now the String is checked to see if it starts with a URI path slash '/'. If it does, then that means you want a UriPathSelector and the URI templating works as previously described. If the URI doesn't start with a '/' then that means you want a full UriSelector which doesn't do placeholder replacement like the UriPathSelector but explodes a  [...]
+2661138 Renamed UriTemplateSelector to UriPathSelector to more accurately reflect its use. Also added UriSelector which is intended to match URIs. It will do exact match on scheme, userInfo, host, port, path, and fragment or it will accept a wildcard '*' for host and '/*' for path. The wildcard splat is invalid in the scheme, so it must be an exact match.
+2a90008 Remove unused tap.
+2acd984 Refactor Actions to reactor.core.action package. Rename flatMap method to mapMany to more accurately reflect what it's purpose is.
+e9d5970 remove unused batch(), fix issue with negative backlog in SingleThreadDispatcher
+25f350b Remove unused consumer Add spec builder for ActorDispatcher
+2b2bfb3 Remove batch() as not really needed
+8428704 fix netty tcp dispatcher to adapt to the new ordering
+7185c90 Increase the sample length back
+6bc7919 fix ThreadPool dispatching Rename BaseDispatcher to SingleThreadDispatcher and promote BaseLifecycleDispatcher as the root Dispatcher
+9a46bf1 Comment ThreadPool test and minor import fix
+e2f3a4b Fix Recursive Dispatching by lazy executing any notify in context. Context is defined per BaseDispatcher with its unique ClassLoader assigned to the consumer thread. In others terms, ring buffer and event loop now implement tail recursion. Synchronous Dispatcher is still blocking on notify. ThreadPoolExecutorDispatcher needs rework. Performances are nearly on par with the previous version and the new design solves dead lock issues. The stack now remains constant (Lazy execution a [...]
+95542ff Fix Recursive dispatching, tail recurse any notify within context Add a new dispatcher type "ActorDispatcher"
+f932d29 first and last can accept explicit batchSize
+691c6fd Change Composable Spec to accept observable, add Streams.on
+d80ffda Add optional OSGi dependencies for reactor-core (fix reactor/reactor#219) & add slf4j-api 1.5.4 compatibility (fix reactor/reactor#220).
+6d70713 Remove interface, rename operation to Action
+7e7ce7c Merge remote-tracking branch 'remotes/origin/master' into improve-stream-promise
+423a58d Merge branch 'move-codec'
+c1f28a1 Refactor the Codec classes from reactor-tcp to reactor-core.
+e2150ab Remove the unnecessary Afterburner Jackson module.
+c974a87 Merge remote-tracking branch 'origin/direct-bytebuf'
+bc7cc36 Bump gradle version to 1.9 and build version to 1.1.0.BUILD-SNAPSHOT
+e39f51f Fixes #217. The NettyTcpConnection was being created by always passing `null` as the `listenAddress` by omitting that CTOR argument. This fix adds a reference to that object to the creation of a connection.
+04ac97f Merge pull request #218 from ifesdjeen/feature/op/tuple-hash
+1785beb Add  and  to Tuples
+c30471a Fix TCP test, rename ForwardOperation to ConnectOperation, Add debugging output for FilterOperation, fix issue in ReactorBuilder
+6e0e922 Fix Perf Tests
+99c72a8 Fix Promise Tests and improve OpUtils
+50348ab Fix promises ctr
+54b791d Add FlatMap operation, Optimize recursive flush, add abstraction for operation pipes such as Composables to implement flush and addOperation
+0f9762c fix dispatcher creation and fix some doc
+228653a fix new Reactor creation
+0fdaaf4 Addresses PR #215 by changing the default behavior of the Netty InboundHandler to attempt to pass the Netty ByteBuf directly to the connection without first turning it into a reactor.io.Buffer.
+a5481ed fix selector test
+251900a fix a few tests, improve debug utils for Composable, Complete Stream functional migration
+64a67fd fix a few tests, improve debug utils for Composable
+0f339ce Decouple Batching Operations from Composable/Stream/Promise attempting to fix #208 and rework PR #177. Add a few utilities to render a Composable tree Work In Progress, not all tests green yet
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0bb2ae7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,55 @@
+# Reactor
+
+`Reactor` is a foundational library building for reactive fast data applications on the JVM. It provides abstractions for Java, Groovy and other JVM languages to make building event and data-driven applications easier. It’s also really fast. On a recent laptop with a dual-core processor, it's possible to process over 15,000,000 events per second with the `RingBufferDispatcher` and over 25,000,000 events per second in a single thread. Other dispatchers are available to provide the develop [...]
+
+[![Build Status](https://drone.io/github.com/reactor/reactor/status.png)](https://drone.io/github.com/reactor/reactor/latest)
+
+### Build instructions
+
+`Reactor` uses a Gradle-based build system. Building the code yourself should be a straightforward case of:
+
+    git clone git at github.com:reactor/reactor.git
+    cd reactor
+    ./gradlew test
+
+This should cause the submodules to be compiled and the tests to be run. To install these artifacts to your local Maven repo, use the handly Gradle Maven plugin:
+
+    ./gradlew install
+
+### Maven Artifacts
+
+Snapshot Maven artifacts are provided in the SpringSource snapshot repositories. To add this repo to your Gradle build, specify the URL like the following:
+
+    ext {
+      reactorVersion = '1.0.0.RELEASE'
+    }
+
+    repositories {
+      maven { url 'http://repo.springsource.org/libs-release' }
+      //maven { url 'http://repo.springsource.org/libs-milestone' }
+      //maven { url 'http://repo.springsource.org/libs-snapshot' }
+      mavenCentral()
+    }
+
+    dependencies {
+      // Reactor Core
+      compile 'org.projectreactor:reactor-core:$reactorVersion'
+      // Reactor Groovy
+      //compile 'org.projectreactor:reactor-groovy:$reactorVersion'
+      // Reactor Spring
+      //compile 'org.projectreactor:reactor-spring:$reactorVersion'
+    }
+
+### Documentation
+
+* [Guides](https://github.com/reactor/reactor/wiki)
+* [API Reference](http://reactor.github.io/docs/api/)
+
+### Community / Support
+
+* [reactor-framework Google Group](https://groups.google.com/forum/?#!forum/reactor-framework)
+* [GitHub Issues](https://github.com/reactor/reactor/issues)
+
+### License
+
+Reactor is [Apache 2.0 licensed](http://www.apache.org/licenses/LICENSE-2.0.html).
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..289bb61
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2011-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+description = 'Reactive fast data framework for the JVM'
+
+ext {
+	gradleVersion = '1.12'
+	gradleScriptDir = "${rootProject.projectDir}/gradle"
+
+	// Languages
+	groovyVersion = '2.3.2'
+
+	// Logging
+	slf4jVersion = '1.7.7'
+	logbackVersion = '1.1.2'
+
+	// Libraries
+	disruptorVersion = '3.2.1'
+	gsCollectionsVersion = '5.1.0'
+	nettyVersion = '4.0.20.Final'
+	jeromqVersion = '0.3.4'
+	jacksonDatabindVersion = '2.4.1.1'
+	jsonPathVersion = '0.9.1'
+	kryoVersion = '2.24.0'
+	protobufVersion = '2.5.0'
+	snappyVersion = '1.1.0.1'
+	hadoopVersion = '1.1.2'
+	openHftChronicleVersion = '2.0.3'
+	openHftLangVersion = '6.1.4'
+
+	// Testing
+	mockitoVersion = '1.9.5'
+	spockVersion = '0.7-groovy-2.0'
+
+	// Code coverage
+	jacocoVersion = '0.7.0.201403182114'
+
+	javadocLinks = [
+			"http://docs.oracle.com/javase/7/docs/api/",
+			"http://docs.oracle.com/javaee/6/api/",
+			"http://fasterxml.github.com/jackson-core/javadoc/2.4.1.1/",
+			"http://www.goldmansachs.com/gs-collections/javadoc/5.1.0/"
+	] as String[]
+}
+
+buildscript {
+	repositories {
+		maven { url "http://repo.spring.io/plugins-release" }
+		jcenter()
+	}
+	dependencies {
+		classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.6',
+							'org.springframework.build.gradle:spring-io-plugin:0.0.3.RELEASE',
+							'com.github.jengelman.gradle.plugins:shadow:0.8'
+	}
+}
+apply from: "$gradleScriptDir/setup.gradle"
+
+configure(allprojects) { project ->
+	group = 'org.projectreactor'
+
+	apply plugin: 'propdeps'
+	apply plugin: 'java'
+	apply from: "${gradleScriptDir}/ide.gradle"
+
+	[compileJava, compileTestJava]*.options*.compilerArgs = [
+			"-Xlint:varargs",
+			"-Xlint:cast",
+			"-Xlint:classfile",
+			"-Xlint:dep-ann",
+			"-Xlint:divzero",
+			"-Xlint:empty",
+			"-Xlint:finally",
+			"-Xlint:overrides",
+			"-Xlint:path",
+			"-Xlint:processing",
+			"-Xlint:static",
+			"-Xlint:try",
+			"-Xlint:deprecation",
+			"-Xlint:unchecked",
+			"-Xlint:-serial",      // intentionally disabled
+			"-Xlint:-options",     // intentionally disabled
+			"-Xlint:-fallthrough", // intentionally disabled
+			"-Xlint:-rawtypes"     // TODO enable and fix warnings
+	]
+
+	compileGroovy {
+		sourceCompatibility = 1.6
+		targetCompatibility = 1.6
+	}
+
+	compileJava {
+		sourceCompatibility = 1.6
+		targetCompatibility = 1.6
+	}
+
+	compileTestJava {
+		sourceCompatibility = 1.8
+		targetCompatibility = 1.8
+	}
+
+	sourceSets.test.resources.srcDirs = ["src/test/resources", "src/test/java"]
+
+	configurations {
+		jacoco
+	}
+
+	configurations.all {
+		exclude group: 'commons-logging', module: 'commons-logging'
+		//exclude module: 'junit'
+	}
+
+	project.tasks.withType(Test).all {
+		systemProperty("java.awt.headless", "true")
+		systemProperty("testGroups", project.properties.get("testGroups"))
+		scanForTestClasses = false
+		include '**/*Tests.*'
+		include '**/*Spec.*'
+		exclude '**/*Abstract*.*'
+	}
+
+	repositories {
+		//mavenLocal()
+		mavenCentral()
+		maven { url 'http://repo.spring.io/libs-snapshot' }
+	}
+
+	// dependencies that are common across all java projects
+	dependencies {
+		// Logging
+		compile "org.slf4j:slf4j-api:$slf4jVersion"
+
+		// JSR-305 annotations
+		optional "com.google.code.findbugs:jsr305:2.0.0"
+
+		// Groovy
+		testCompile "org.codehaus.groovy:groovy-all:$groovyVersion"
+
+		// Testing
+		testCompile "org.spockframework:spock-core:$spockVersion",
+								"org.hamcrest:hamcrest-library:1.3"
+		testRuntime "ch.qos.logback:logback-classic:$logbackVersion"
+
+		// Code coverage
+		jacoco "org.jacoco:org.jacoco.agent:$jacocoVersion:runtime"
+	}
+}
+
+if (JavaVersion.current().isJava8Compatible()) {
+	allprojects {
+		compileTestJava.options.compilerArgs += "-parameters"
+		tasks.withType(Javadoc) {
+			options.addStringOption('Xdoclint:none', '-quiet')
+		}
+	}
+}
+
+configure(subprojects) { subproject ->
+	if (project.hasProperty('platformVersion')) {
+		apply plugin: 'spring-io'
+
+		repositories {
+			maven { url 'http://repo.spring.io/libs-snapshot' }
+		}
+
+		dependencies {
+			springIoVersions "io.spring.platform:platform-versions:$platformVersion at properties"
+		}
+	}
+
+	test {
+		testLogging {
+			jvmArgs "-javaagent:${configurations.jacoco.asPath}=destfile=${buildDir}/jacoco.exec,includes=reactor.*"
+			events "failed"
+			exceptionFormat "full"
+		}
+	}
+}
+
+configure(rootProject) {
+	description = "Reactor"
+
+	configurations.archives.artifacts.clear()
+
+	task api(type: Javadoc) {
+		group = "Documentation"
+		description = "Generates aggregated Javadoc API documentation."
+		title = "${rootProject.description} ${version} API"
+
+		dependsOn {
+			subprojects.collect {
+				it.tasks.getByName("jar")
+			}
+		}
+		options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
+		options.author = true
+		options.header = rootProject.description
+		options.overview = "src/api/overview.html"
+		options.stylesheetFile = file("src/api/stylesheet.css")
+		options.links(project.ext.javadocLinks)
+
+		source subprojects.collect { project ->
+			project.sourceSets.main.allJava
+		}
+
+		maxMemory = "1024m"
+		destinationDir = new File(buildDir, "api")
+
+		doFirst {
+			classpath = files(subprojects.collect { it.sourceSets.main.compileClasspath })
+		}
+	}
+}
+
+project('reactor-core') {
+	description = 'Core Reactor components'
+
+	apply plugin: 'osgi'
+
+	ext.bundleImportPackages = [
+			'net.openhft.chronicle;resolution:=optional',
+			'net.openhft.chronicle.tools;resolution:=optional',
+			'com.fasterxml.jackson.core;resolution:=optional',
+			'com.fasterxml.jackson.databind;resolution:=optional',
+			'com.fasterxml.jackson.databind.node;resolution:=optional',
+			'com.fasterxml.jackson.databind.type;resolution:=optional',
+			'com.jayway.jsonpath;resolution:=optional',
+			'com.jayway.jsonpath.internal;resolution:=optional',
+			'com.jayway.jsonpath.spi;resolution:=optional',
+			'org.slf4j;version="[1.5.4,2)"',
+			'*'
+	]
+
+	dependencies {
+		// High-speed Dispatching
+		compile "com.lmax:disruptor:$disruptorVersion"
+
+		// Rich Collections
+		compile "com.goldmansachs:gs-collections:$gsCollectionsVersion",
+						"io.gatling:jsr166e:1.0"
+
+		// High-speed Messaging
+		optional "net.openhft:chronicle:$openHftChronicleVersion",
+						 "net.openhft:lang:$openHftLangVersion"
+
+		// JSON handling
+		optional "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion",
+						 "com.jayway.jsonpath:json-path:$jsonPathVersion"
+
+		// Serialization
+		optional "com.esotericsoftware.kryo:kryo:$kryoVersion",
+						 "com.google.protobuf:protobuf-java:$protobufVersion",
+						 "org.xerial.snappy:snappy-java:$snappyVersion"
+	}
+
+	jar {
+		manifest {
+			instruction 'Import-Package', bundleImportPackages.join(',')
+		}
+	}
+}
+
+project('reactor-groovy-extensions') {
+	description = 'Reactor Groovy Ext components'
+
+	apply plugin: 'osgi'
+	apply plugin: 'groovy'
+
+	sonarRunner {
+		sonarProperties {
+			property "sonar.language", "grvy"
+		}
+	}
+
+	dependencies {
+		compile project(':reactor-core'),
+						"org.codehaus.groovy:groovy-all:$groovyVersion"
+	}
+}
+
+project('reactor-groovy') {
+	description = 'Reactor Groovy components'
+
+	apply plugin: 'osgi'
+	apply plugin: 'groovy'
+
+	sonarRunner {
+		sonarProperties {
+			property "sonar.language", "grvy"
+		}
+	}
+
+	dependencies {
+		compile project(':reactor-core'),
+						project(':reactor-groovy-extensions'),
+						"org.codehaus.groovy:groovy-all:$groovyVersion"
+	}
+}
+
+project('reactor-logback') {
+	description = 'Async Logback appender implementation'
+
+	apply plugin: 'application'
+	apply plugin: 'shadow'
+
+	mainClassName = "reactor.logback.DurableLogUtility"
+	ext.baseName = "${archivesBaseName}-${version}"
+
+	dependencies {
+		compile project(':reactor-core'),
+						"ch.qos.logback:logback-classic:$logbackVersion",
+						"net.openhft:chronicle:$openHftChronicleVersion",
+						"commons-cli:commons-cli:1.2"
+	}
+
+	shadow {
+		artifactSet {
+			exclude 'ch.qos.logback'
+			exclude 'org.slf4j'
+			exclude 'org.intellij'
+			exclude 'org.jetbrains'
+			exclude 'META-INF/maven/org.slf4j'
+		}
+	}
+}
+
+project('reactor-net') {
+	description = 'Reactor TCP components'
+
+	apply plugin: 'osgi'
+
+	ext.bundleImportPackages = [
+			'org.zeromq;resolution:=optional',
+			'com.fasterxml.jackson.core;resolution:=optional',
+			'com.fasterxml.jackson.databind;resolution:=optional',
+			'com.fasterxml.jackson.module;resolution:=optional',
+			'*'
+	]
+
+	dependencies {
+		compile project(':reactor-core')
+
+		compile "io.netty:netty-all:$nettyVersion"
+
+		optional "org.zeromq:jeromq:$jeromqVersion",
+						 "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion"
+
+		// Testing
+		testCompile "org.apache.hadoop:hadoop-client:$hadoopVersion",
+								"com.esotericsoftware.kryo:kryo:$kryoVersion"
+		testRuntime project(':reactor-logback'),
+								"org.slf4j:jcl-over-slf4j:$slf4jVersion"
+	}
+
+	jar {
+		manifest {
+			instruction 'Import-Package', bundleImportPackages.join(',')
+		}
+	}
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..041ae49
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+version=1.1.6.RELEASE
diff --git a/gradle/ide.gradle b/gradle/ide.gradle
new file mode 100644
index 0000000..23cb623
--- /dev/null
+++ b/gradle/ide.gradle
@@ -0,0 +1,109 @@
+import org.gradle.plugins.ide.eclipse.model.ProjectDependency
+import org.gradle.plugins.ide.eclipse.model.SourceFolder
+
+apply plugin: 'idea'
+apply plugin: 'eclipse'
+apply plugin: 'propdeps-eclipse'
+apply plugin: 'propdeps-idea'
+
+// Until eclipse fully supports Java 8 use 1.7 source level
+eclipse.jdt {
+	sourceCompatibility = 1.7
+	targetCompatibility = 1.7
+}
+
+// Replace classpath entries with project dependencies (GRADLE-1116)
+eclipse.classpath.file.whenMerged { classpath ->
+	def regexp = /.*?\/([^\/]+)\/build\/[^\/]+\/(?:main|test)/ // only match those that end in main or test (avoids removing necessary entries like build/classes/jaxb)
+	def projectOutputDependencies = classpath.entries.findAll { entry -> entry.path=~regexp }
+	projectOutputDependencies.each { entry ->
+		def matcher = (entry.path=~regexp)
+		if (matcher) {
+			def projectName = matcher[0][1]
+			def path = "/${projectName}"
+			if (!classpath.entries.find { e -> e instanceof ProjectDependency && e.path == path }) {
+				def dependency = new ProjectDependency(path, project(":${projectName}").path)
+				dependency.exported = true
+				classpath.entries.add(dependency)
+			}
+			classpath.entries.remove(entry)
+		}
+	}
+	classpath.entries.removeAll { entry -> (entry.path=~/(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) }
+}
+
+// Use separate main/test outputs (prevents WTP from packaging test classes)
+eclipse.classpath.defaultOutputDir = file(project.name + "/bin/eclipse")
+eclipse.classpath.file.beforeMerged { classpath ->
+	classpath.entries.findAll { it instanceof SourceFolder }.each {
+		if (it.output?.startsWith("bin/")) {
+			it.output = null
+		}
+	}
+}
+eclipse.classpath.file.whenMerged { classpath ->
+	classpath.entries.findAll { it instanceof SourceFolder }.each {
+		it.output = "bin/" + it.path.split("/")[1]
+	}
+}
+
+// Allow projects to be used as WPT modules
+eclipse.project.natures "org.eclipse.wst.common.project.facet.core.nature"
+
+// Include project specific settings
+task eclipseSettings(type: Copy) {
+	from rootProject.files(
+			"src/eclipse/org.eclipse.jdt.ui.prefs",
+			"src/eclipse/org.eclipse.wst.common.project.facet.core.xml")
+	into project.file('.settings/')
+	outputs.upToDateWhen { false }
+}
+
+task eclipseWstComponent(type: Copy) {
+	from rootProject.files(
+			"src/eclipse/org.eclipse.wst.common.component")
+	into project.file('.settings/')
+	expand(deployname: project.name)
+	outputs.upToDateWhen { false }
+}
+
+task eclipseJdtPrepare(type: Copy) {
+	from rootProject.file("src/eclipse/org.eclipse.jdt.core.prefs")
+	into project.file(".settings/")
+	outputs.upToDateWhen { false }
+}
+
+task cleanEclipseJdtUi(type: Delete) {
+	delete project.file(".settings/org.eclipse.jdt.ui.prefs")
+	delete project.file("org.eclipse.jdt.core.prefs")
+	delete project.file(".settings/org.eclipse.wst.common.component")
+	delete project.file(".settings/org.eclipse.wst.common.project.facet.core.xml")
+}
+
+tasks["eclipseJdt"].dependsOn(eclipseJdtPrepare)
+tasks["cleanEclipse"].dependsOn(cleanEclipseJdtUi)
+tasks["eclipse"].dependsOn(eclipseSettings, eclipseWstComponent)
+
+// Filter 'build' folder
+
+eclipse.project.file.withXml {
+	def node = it.asNode()
+
+	def filteredResources = node.get("filteredResources")
+	if (filteredResources) {
+		node.remove(filteredResources)
+	}
+	def filterNode = node.appendNode("filteredResources").appendNode("filter")
+	filterNode.appendNode("id", "1359048889071")
+	filterNode.appendNode("name", "")
+	filterNode.appendNode("type", "30")
+	def matcherNode = filterNode.appendNode("matcher")
+	matcherNode.appendNode("id", "org.eclipse.ui.ide.multiFilter")
+	matcherNode.appendNode("arguments", "1.0-projectRelativePath-matches-false-false-build")
+}
+
+idea {
+    module {
+        scopes.PROVIDED.plus += configurations.optional
+    }
+}
\ No newline at end of file
diff --git a/gradle/setup.gradle b/gradle/setup.gradle
new file mode 100644
index 0000000..02987c5
--- /dev/null
+++ b/gradle/setup.gradle
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2011-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+apply plugin: 'groovy'
+apply plugin: 'sonar-runner'
+
+sonarRunner {
+	sonarProperties {
+		property "sonar.host.url", "$sonarHostUrl"
+		property "sonar.jdbc.url", "$sonarJdbcUrl"
+		property "sonar.jdbc.driverClassName", "$sonarJdbcDriver"
+		property "sonar.jdbc.username", "$sonarJdbcUsername"
+		property "sonar.jdbc.password", "$sonarJdbcPassword"
+		property "sonar.core.codeCoveragePlugin", "jacoco"
+		property "sonar.jacoco.reportPath", "${buildDir.name}/jacoco.exec"
+		property "sonar.links.ci", "https://build.springsource.org/browse/REACTOR-CORE"
+		property "sonar.links.issue", "https://github.com/reactor/reactor/issues"
+		property "sonar.links.scm", "https://github.com/reactor/reactor.git"
+	}
+}
+
+task wrapper(type: Wrapper, description: "Create a Gradle self-download wrapper") {
+	group = 'Project Setup'
+	gradleVersion = "$gradleVersion"
+}
+
+configure(subprojects) { subproject ->
+	apply plugin: 'propdeps-maven'
+	apply plugin: 'maven'
+
+	install {
+		repositories.mavenInstaller {
+			customizePom(pom, subproject)
+		}
+	}
+
+	jar {
+		manifest.attributes["Created-By"] = "${System.getProperty("java.version")} (${System.getProperty("java.specification.vendor")})"
+		manifest.attributes["Implementation-Title"] = subproject.name
+		manifest.attributes["Implementation-Version"] = subproject.version
+	}
+
+	task sourcesJar(type: Jar) {
+		classifier = 'sources'
+		from sourceSets.main.allSource
+	}
+
+	task groovydocJar(type: Jar) {
+		classifier = 'groovydoc'
+		from groovydoc
+	}
+
+	task javadocJar(type: Jar) {
+		classifier = 'javadoc'
+		from javadoc
+	}
+
+	artifacts {
+		archives sourcesJar
+		archives javadocJar
+		archives groovydocJar
+	}
+}
+
+def customizePom(def pom, def gradleProject) {
+	pom.whenConfigured { generatedPom ->
+		// eliminate test-scoped dependencies (no need in maven central poms)
+		generatedPom.dependencies.removeAll { dep ->
+			dep.scope == "test"
+		}
+
+		// sort to make pom dependencies order consistent to ease comparison of older poms
+		generatedPom.dependencies = generatedPom.dependencies.sort { dep ->
+			"$dep.scope:$dep.groupId:$dep.artifactId"
+		}
+
+		// add all items necessary for maven central publication
+		generatedPom.project {
+			name = gradleProject.description
+			description = gradleProject.description
+			url = 'https://github.com/reactor/reactor'
+			organization {
+				name = 'reactor'
+				url = 'http://github.com/reactor'
+			}
+			licenses {
+				license {
+					name 'The Apache Software License, Version 2.0'
+					url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+					distribution 'repo'
+				}
+			}
+			scm {
+				url = 'https://github.com/reactor/reactor'
+				connection = 'scm:git:git://github.com/reactor/reactor'
+				developerConnection = 'scm:git:git://github.com/reactor/reactor'
+			}
+			developers {
+				developer {
+					id = 'smaldini'
+					name = 'Stephane Maldini'
+					email = 'smaldini at gopivotal.com'
+				}
+				developer {
+					id = 'jbrisbin'
+					name = 'Jon Brisbin'
+					email = 'jbrisbin at gopivotal.com'
+				}
+			}
+			issueManagement {
+				system = "GitHub Issues"
+				url = "https://github.com/reactor/reactor/issues"
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/alloc/AbstractReference.java b/reactor-core/src/main/java/reactor/alloc/AbstractReference.java
new file mode 100644
index 0000000..a5dfc1e
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/AbstractReference.java
@@ -0,0 +1,70 @@
+package reactor.alloc;
+
+import reactor.timer.TimeUtils;
+
+/**
+ * An abstract {@link reactor.alloc.Reference} implementation that does reference counting.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public abstract class AbstractReference<T extends Recyclable> implements Reference<T> {
+
+	private volatile int refCnt = 0;
+
+	private final long inception;
+	private final T    obj;
+
+	protected AbstractReference(T obj) {
+		this.obj = obj;
+		this.inception = TimeUtils.approxCurrentTimeMillis();
+	}
+
+	@Override
+	public long getAge() {
+		return TimeUtils.approxCurrentTimeMillis() - inception;
+	}
+
+	@Override
+	public int getReferenceCount() {
+		return refCnt;
+	}
+
+	@Override
+	public void retain() {
+		retain(1);
+	}
+
+	@Override
+	public void retain(int incr) {
+		refCnt += incr;
+	}
+
+	@Override
+	public void release() {
+		release(1);
+	}
+
+	@Override
+	public void release(int decr) {
+		refCnt -= Math.min(decr, refCnt);
+		if(refCnt < 1) {
+			obj.recycle();
+		}
+	}
+
+	@Override
+	public T get() {
+		return obj;
+	}
+
+	@Override
+	public String toString() {
+		return "Reference{" +
+				"refCnt=" + refCnt +
+				", inception=" + inception +
+				", obj=" + obj +
+				'}';
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/Allocator.java b/reactor-core/src/main/java/reactor/alloc/Allocator.java
new file mode 100644
index 0000000..a1c4c1d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/Allocator.java
@@ -0,0 +1,39 @@
+package reactor.alloc;
+
+import java.util.List;
+
+/**
+ * An {@code Allocator} is responsible for returning to the caller a {@link reactor.alloc.Reference} to a reusable
+ * object or to provide a newly-created object, depending on the underlying allocation strategy.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public interface Allocator<T extends Recyclable> {
+
+	/**
+	 * Allocate an object from the internal pool.
+	 *
+	 * @return a {@link reactor.alloc.Reference} that can be retained and released.
+	 */
+	Reference<T> allocate();
+
+	/**
+	 * Allocate a batch of objects all at once.
+	 *
+	 * @param size
+	 * 		the number of objects to allocate
+	 *
+	 * @return a {@code List} of {@link Reference References} to the allocated objects
+	 */
+	List<Reference<T>> allocateBatch(int size);
+
+	/**
+	 * Efficiently release a batch of References all at once.
+	 *
+	 * @param batch
+	 * 		the references to release
+	 */
+	void release(List<Reference<T>> batch);
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/PartitionedAllocator.java b/reactor-core/src/main/java/reactor/alloc/PartitionedAllocator.java
new file mode 100644
index 0000000..71c4bb1
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/PartitionedAllocator.java
@@ -0,0 +1,71 @@
+package reactor.alloc;
+
+import jsr166e.ConcurrentHashMapV8;
+import reactor.function.Supplier;
+
+import java.util.List;
+
+/**
+ * An {@link reactor.alloc.Allocator} implementation that allocates a single object per thread, similar to a {@link
+ * java.lang.ThreadLocal} but more efficient. Objects created here are never released.
+ *
+ * @author Jon Brisbin
+ */
+public abstract class PartitionedAllocator<T extends Recyclable> implements Allocator<T> {
+
+	private final ConcurrentHashMapV8<Long, Reference<T>>     partitions = new ConcurrentHashMapV8<Long, Reference<T>>();
+	private final ConcurrentHashMapV8.Fun<Long, Reference<T>> newRefFn   = new ConcurrentHashMapV8.Fun<Long, Reference<T>>() {
+		@Override
+		public Reference<T> apply(Long partId) {
+			return new PartitionedReference<T>(factory.get(), partId);
+		}
+	};
+
+	private final Supplier<T> factory;
+
+	protected PartitionedAllocator(Supplier<T> factory) {
+		this.factory = factory;
+	}
+
+	@Override
+	public Reference<T> allocate() {
+		long partId = nextPartitionId();
+		Reference<T> ref;
+		if (null != (ref = partitions.get(partId))
+				|| null != (ref = partitions.computeIfAbsent(partId, newRefFn))) {
+			return ref;
+		}
+		throw new IllegalStateException("Could not allocate from " + factory + " for thread " + Thread.currentThread());
+	}
+
+	@Override
+	public List<Reference<T>> allocateBatch(int size) {
+		throw new IllegalStateException("PartitionedAllocators don't allocate via batch");
+	}
+
+	@Override
+	public void release(List<Reference<T>> batch) {
+		for (Reference<T> ref : batch) {
+			if (ref instanceof PartitionedReference) {
+				long partId = ((PartitionedReference) ref).getPartitionId();
+				partitions.get(partId).release();
+			}
+		}
+	}
+
+	protected abstract long nextPartitionId();
+
+	private static class PartitionedReference<T extends Recyclable> extends AbstractReference<T> {
+		private final long partitionId;
+
+		public PartitionedReference(T obj, long partitionId) {
+			super(obj);
+			this.partitionId = partitionId;
+		}
+
+		public long getPartitionId() {
+			return partitionId;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/Recyclable.java b/reactor-core/src/main/java/reactor/alloc/Recyclable.java
new file mode 100644
index 0000000..5ea1b5a
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/Recyclable.java
@@ -0,0 +1,16 @@
+package reactor.alloc;
+
+/**
+ * A simple interface that marks an object as being recyclable.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public interface Recyclable {
+
+	/**
+	 * Free any internal resources and reset the state of the object to enable reuse.
+	 */
+	void recycle();
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/RecyclableNumber.java b/reactor-core/src/main/java/reactor/alloc/RecyclableNumber.java
new file mode 100644
index 0000000..571200f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/RecyclableNumber.java
@@ -0,0 +1,51 @@
+package reactor.alloc;
+
+/**
+ * @author Jon Brisbin
+ */
+public class RecyclableNumber extends Number implements Recyclable {
+
+	private volatile double value = -1;
+
+	public void setValue(int value) {
+		this.value = value;
+	}
+
+	public void setValue(long value) {
+		this.value = value;
+	}
+
+	public void setValue(float value) {
+		this.value = value;
+	}
+
+	public void setValue(double value) {
+		this.value = value;
+	}
+
+	@Override
+	public int intValue() {
+		return (int) value;
+	}
+
+	@Override
+	public long longValue() {
+		return (long) value;
+	}
+
+	@Override
+	public float floatValue() {
+		return (float) value;
+	}
+
+	@Override
+	public double doubleValue() {
+		return value;
+	}
+
+	@Override
+	public void recycle() {
+		value = -1;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/RecyclableString.java b/reactor-core/src/main/java/reactor/alloc/RecyclableString.java
new file mode 100644
index 0000000..d418c50
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/RecyclableString.java
@@ -0,0 +1,24 @@
+package reactor.alloc;
+
+/**
+ * @author Jon Brisbin
+ */
+public class RecyclableString implements Recyclable {
+
+	private volatile String value = "";
+
+	public void setValue(String value) {
+		this.value = (null == value ? "" : value);
+	}
+
+	@Override
+	public void recycle() {
+		this.value = "";
+	}
+
+	@Override
+	public String toString() {
+		return value;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/Reference.java b/reactor-core/src/main/java/reactor/alloc/Reference.java
new file mode 100644
index 0000000..abbe1ba
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/Reference.java
@@ -0,0 +1,53 @@
+package reactor.alloc;
+
+import reactor.function.Supplier;
+
+/**
+ * A {@code Reference} provides access to and metadata about a poolable object.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public interface Reference<T extends Recyclable> extends Supplier<T> {
+
+	/**
+	 * Get the age of this {@code Reference} since it's creation.
+	 *
+	 * @return the number of milliseconds since this {@code Reference} was created.
+	 */
+	long getAge();
+
+	/**
+	 * Get the current number of references retained to this object.
+	 *
+	 * @return the reference count.
+	 */
+	int getReferenceCount();
+
+	/**
+	 * Increase reference count by {@literal 1}.
+	 */
+	void retain();
+
+	/**
+	 * Increase reference count by {@literal incr} amount.
+	 *
+	 * @param incr
+	 * 		the amount to increment the reference count.
+	 */
+	void retain(int incr);
+
+	/**
+	 * Decrease the reference count by {@literal 1}.
+	 */
+	void release();
+
+	/**
+	 * Decrease the reference count by {@literal incr} amount.
+	 *
+	 * @param decr
+	 * 		the amount to decrement the reference count.
+	 */
+	void release(int decr);
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/ReferenceCountingAllocator.java b/reactor-core/src/main/java/reactor/alloc/ReferenceCountingAllocator.java
new file mode 100644
index 0000000..3c20349
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/ReferenceCountingAllocator.java
@@ -0,0 +1,147 @@
+package reactor.alloc;
+
+import reactor.function.Supplier;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * An implementation of {@link reactor.alloc.Allocator} that uses reference counting to determine when an object
+ * should
+ * be recycled and placed back into the pool to be reused.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public class ReferenceCountingAllocator<T extends Recyclable> implements Allocator<T> {
+
+	private static final int DEFAULT_INITIAL_SIZE = 2048;
+
+	private final ReentrantLock           refLock    = new ReentrantLock();
+	private final ReentrantLock           leaseLock  = new ReentrantLock();
+	private final ArrayList<Reference<T>> references = new ArrayList<Reference<T>>();
+	private final Supplier<T> factory;
+
+	private volatile BitSet leaseMask;
+
+	public ReferenceCountingAllocator(Supplier<T> factory) {
+		this(DEFAULT_INITIAL_SIZE, factory);
+	}
+
+	public ReferenceCountingAllocator(int initialSize, Supplier<T> factory) {
+		this.factory = factory;
+		this.references.ensureCapacity(initialSize);
+		this.leaseMask = new BitSet(initialSize);
+		expand(initialSize);
+	}
+
+	@Override
+	public Reference<T> allocate() {
+		Reference<T> ref;
+		int len = refCnt();
+		int next;
+
+		leaseLock.lock();
+		try {
+			next = leaseMask.nextClearBit(0);
+			if (next >= len) {
+				expand(len);
+			}
+			leaseMask.set(next);
+		} finally {
+			leaseLock.unlock();
+		}
+
+		if (next < 0) {
+			throw new RuntimeException("Allocator is exhausted.");
+		}
+
+		ref = references.get(next);
+		if (null == ref) {
+			// this reference has been nulled somehow.
+			// that's not really critical, just replace it.
+			refLock.lock();
+			try {
+				ref = new ReferenceCountingAllocatorReference<T>(factory.get(), next);
+				references.set(next, ref);
+			} finally {
+				refLock.unlock();
+			}
+		}
+		ref.retain();
+
+		return ref;
+	}
+
+	@Override
+	public List<Reference<T>> allocateBatch(int size) {
+		List<Reference<T>> refs = new ArrayList<Reference<T>>(size);
+		for (int i = 0; i < size; i++) {
+			refs.add(allocate());
+		}
+		return refs;
+	}
+
+	@Override
+	public void release(List<Reference<T>> batch) {
+		if (null != batch && !batch.isEmpty()) {
+			for (Reference<T> ref : batch) {
+				ref.release();
+			}
+		}
+	}
+
+	private int refCnt() {
+		refLock.lock();
+		try {
+			return references.size();
+		} finally {
+			refLock.unlock();
+		}
+	}
+
+	private void expand(int num) {
+		refLock.lock();
+		try {
+			int len = references.size();
+			int newLen = len + num;
+			for (int i = len; i <= newLen; i++) {
+				references.add(new ReferenceCountingAllocatorReference<T>(factory.get(), i));
+			}
+			BitSet newLeaseMask = new BitSet(newLen);
+			int leases = leaseMask.length();
+			for (int i = 0; i < leases; i++) {
+				newLeaseMask.set(i, leaseMask.get(i));
+			}
+			leaseMask = newLeaseMask;
+		} finally {
+			refLock.unlock();
+		}
+	}
+
+	private class ReferenceCountingAllocatorReference<T extends Recyclable> extends AbstractReference<T> {
+		private final int bit;
+
+		private ReferenceCountingAllocatorReference(T obj, int bit) {
+			super(obj);
+			this.bit = bit;
+		}
+
+		@Override
+		public void release(int decr) {
+			leaseLock.lock();
+			try {
+				super.release(decr);
+				if (getReferenceCount() < 1) {
+					// There won't be contention to clear this
+					leaseMask.clear(bit);
+				}
+			} finally {
+				leaseLock.unlock();
+			}
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/RingBufferAllocator.java b/reactor-core/src/main/java/reactor/alloc/RingBufferAllocator.java
new file mode 100644
index 0000000..37bba65
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/RingBufferAllocator.java
@@ -0,0 +1,205 @@
+package reactor.alloc;
+
+import com.lmax.disruptor.*;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+import reactor.function.Supplier;
+import reactor.support.Identifiable;
+import reactor.support.NamedDaemonThreadFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@code Allocator} implementation based int the LMAX Disruptor {@code RingBuffer}.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public class RingBufferAllocator<T extends Recyclable> implements Allocator<T> {
+
+	private final ExecutorService executor;
+	private final boolean         shutdownExecutor;
+
+	private final Disruptor<Reference<T>>  disruptor;
+	private       RingBuffer<Reference<T>> ringBuffer;
+
+	public RingBufferAllocator(String name, int poolSize, Supplier<T> poolFactory) {
+		this(name, poolSize, poolFactory, 1);
+	}
+
+	public RingBufferAllocator(String name, int poolSize, Supplier<T> poolFactory, int eventThreads) {
+		this(name, poolSize, poolFactory, eventThreads, null, null, ProducerType.MULTI, new BlockingWaitStrategy(), null);
+	}
+
+	@SuppressWarnings("unchecked")
+	public RingBufferAllocator(String name,
+	                           int poolSize,
+	                           final Supplier<T> poolFactory,
+	                           int eventThreads,
+	                           final EventHandler<Reference<T>> eventHandler,
+	                           final ExceptionHandler errorHandler,
+	                           ProducerType producerType,
+	                           WaitStrategy waitStrategy,
+	                           ExecutorService executor) {
+		if(null == executor) {
+			this.executor = Executors.newFixedThreadPool(eventThreads, new NamedDaemonThreadFactory(name));
+			this.shutdownExecutor = true;
+		} else {
+			this.executor = executor;
+			this.shutdownExecutor = false;
+		}
+
+		this.disruptor = new Disruptor<Reference<T>>(
+				new EventFactory<Reference<T>>() {
+					@SuppressWarnings("rawtypes")
+					@Override
+					public Reference<T> newInstance() {
+						return new RingBufferReference(poolFactory.get());
+					}
+				},
+				poolSize,
+				this.executor,
+				producerType,
+				waitStrategy
+		);
+		if(null != errorHandler) {
+			disruptor.handleExceptionsWith(errorHandler);
+		}
+		if(null != eventHandler) {
+			if(eventThreads > 1) {
+				WorkHandler<Reference<T>>[] workHandlers = new WorkHandler[eventThreads];
+				for(int i = 0; i < eventThreads; i++) {
+					workHandlers[i] = new WorkHandler<Reference<T>>() {
+						@Override
+						public void onEvent(Reference<T> ref) throws Exception {
+							eventHandler.onEvent(ref, -1, false);
+						}
+					};
+				}
+				disruptor.handleEventsWithWorkerPool(workHandlers);
+			} else {
+				disruptor.handleEventsWith(eventHandler);
+			}
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public Reference<T> allocate() {
+		long l = ringBuffer.next();
+		RingBufferReference ref = (RingBufferReference)ringBuffer.get(l);
+		ref.setSequenceId(l);
+		ref.retain();
+		return ref;
+	}
+
+	public List<Reference<T>> allocateBatch(int size, List<Reference<T>> refs) {
+		for(int i = 0; i < size; i++) {
+			Reference<T> ref = allocate();
+			refs.add(ref);
+		}
+		return refs;
+	}
+
+	@Override
+	public List<Reference<T>> allocateBatch(int size) {
+		return allocateBatch(size, new ArrayList<Reference<T>>(size));
+	}
+
+	@Override
+	public void release(List<Reference<T>> batch) {
+		if(null == batch || batch.isEmpty()) {
+			return;
+		}
+		long start = ((RingBufferReference)batch.get(0)).sequenceId;
+		int len = batch.size();
+		if(len > 1) {
+			long end = ((RingBufferReference)batch.get(len - 1)).sequenceId;
+			ringBuffer.publish(start, end);
+		} else {
+			if(!ringBuffer.isPublished(start)) {
+				ringBuffer.publish(start);
+			}
+		}
+		batch.clear();
+	}
+
+	public boolean alive() {
+		return !executor.isShutdown();
+	}
+
+	public void start() {
+		ringBuffer = disruptor.start();
+	}
+
+	public boolean awaitAndShutdown() {
+		return awaitAndShutdown(Integer.MAX_VALUE, TimeUnit.SECONDS);
+	}
+
+	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
+		try {
+			if(shutdownExecutor) {
+				return executor.awaitTermination(timeout, timeUnit);
+			}
+		} catch(InterruptedException e) {
+			Thread.currentThread().interrupt();
+			return false;
+		} finally {
+			shutdown();
+		}
+		return true;
+	}
+
+	public void shutdown() {
+		disruptor.shutdown();
+		if(shutdownExecutor) {
+			executor.shutdown();
+		}
+	}
+
+	public void halt() {
+		if(shutdownExecutor) {
+			executor.shutdownNow();
+		}
+		disruptor.halt();
+	}
+
+	private class RingBufferReference extends AbstractReference<T> {
+		private final    boolean isIdentifiable;
+		private volatile long    sequenceId;
+
+		private RingBufferReference(T obj) {
+			super(obj);
+			this.isIdentifiable = Identifiable.class.isInstance(obj);
+		}
+
+		@SuppressWarnings("unchecked")
+		public void setSequenceId(long sequenceId) {
+			this.sequenceId = sequenceId;
+			if(isIdentifiable) {
+				((Identifiable<Long>)get()).setId(sequenceId);
+			}
+		}
+
+		@Override
+		public void release(int decr) {
+			if(!ringBuffer.isPublished(sequenceId)) {
+				// No one else is currently accessing this reference so we can
+				// publish to the RingBuffer, causing the EventHandler to run.
+				ringBuffer.publish(sequenceId);
+				// Don't actually release this until the EventHandler has run.
+				// This is a different situation than other Reference implementations
+				// that usually want immediately clearing of resources.
+				if(1 == decr) {
+					return;
+				}
+			}
+			super.release(decr);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/ThreadPartitionedAllocator.java b/reactor-core/src/main/java/reactor/alloc/ThreadPartitionedAllocator.java
new file mode 100644
index 0000000..510b032
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/ThreadPartitionedAllocator.java
@@ -0,0 +1,19 @@
+package reactor.alloc;
+
+import reactor.function.Supplier;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ThreadPartitionedAllocator<T extends Recyclable> extends PartitionedAllocator<T> {
+
+	public ThreadPartitionedAllocator(Supplier<T> factory) {
+		super(factory);
+	}
+
+	@Override
+	protected long nextPartitionId() {
+		return Thread.currentThread().getId();
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/factory/BatchFactorySupplier.java b/reactor-core/src/main/java/reactor/alloc/factory/BatchFactorySupplier.java
new file mode 100644
index 0000000..ae9b4b1
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/factory/BatchFactorySupplier.java
@@ -0,0 +1,90 @@
+package reactor.alloc.factory;
+
+import reactor.function.Supplier;
+
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Simple {@link reactor.function.Supplier} implementation that fills a fixed-size array with
+ * pre-allocated objects, which are handed out, one after the other, until the array is exhausted.
+ * At that point, the array is re-filled by creating more objects from the supplied factory and
+ * those objects are then handed out one at a time. There is no limit to the number of items
+ * created by the pool because references to the created objects are not maintained once it
+ * has been retrieved from the pool.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public class BatchFactorySupplier<T> implements Supplier<T> {
+
+	private static final AtomicIntegerFieldUpdater<BatchFactorySupplier> NEXT_UPD
+			= AtomicIntegerFieldUpdater.newUpdater(BatchFactorySupplier.class, "next");
+
+	private final ReentrantLock fillLock = new ReentrantLock(true);
+
+	private final int         size;
+	private final Supplier<T> factory;
+	private final T[]         objs;
+
+	private volatile int next = 0;
+
+	@SuppressWarnings("unchecked")
+	public BatchFactorySupplier(int size, Supplier<T> factory) {
+		this.size = size;
+		this.factory = factory;
+		this.objs = (T[])new Object[size];
+		for(int i = 0; i < size; i++) {
+			objs[i] = factory.get();
+		}
+	}
+
+	/**
+	 * Returns the number of items being pooled.
+	 *
+	 * @return the size of the pool
+	 */
+	public int size() {
+		return size;
+	}
+
+	/**
+	 * How many unallocated items remain in the pool.
+	 *
+	 * @return the size of the unallocated pool
+	 */
+	public int remaining() {
+		return size - next;
+	}
+
+	@Override
+	public T get() {
+		int next = NEXT_UPD.getAndIncrement(this);
+		if(next >= size) {
+			fill();
+			return get();
+		}
+
+		T obj = objs[next];
+		// wipe this reference
+		objs[next] = null;
+
+		return obj;
+	}
+
+	protected void fill() {
+		fillLock.lock();
+		try {
+			// check again to see if the pool was filled by another thread
+			if(this.next >= size) {
+				for(int i = 0; i < size; i++) {
+					objs[i] = factory.get();
+				}
+				NEXT_UPD.set(this, 0);
+			}
+		} finally {
+			fillLock.unlock();
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/factory/EventFactorySupplier.java b/reactor-core/src/main/java/reactor/alloc/factory/EventFactorySupplier.java
new file mode 100644
index 0000000..b282d93
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/factory/EventFactorySupplier.java
@@ -0,0 +1,26 @@
+package reactor.alloc.factory;
+
+import reactor.event.Event;
+import reactor.function.Supplier;
+
+/**
+ * A {@link reactor.function.Supplier} implementation that instantiates Events
+ * based on Event data type.
+ *
+ * @param <T> type of {@link reactor.event.Event} data
+ * @author Oleksandr Petrov
+ * @since 1.1
+ */
+public class EventFactorySupplier<T> implements Supplier<Event<T>> {
+
+  private final Class<T> klass;
+
+  public EventFactorySupplier(Class<T> klass) {
+    this.klass = klass;
+  }
+
+  @Override
+  public Event<T> get() {
+    return new Event<T>(klass);
+  }
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/factory/Factories.java b/reactor-core/src/main/java/reactor/alloc/factory/Factories.java
new file mode 100644
index 0000000..e88d001
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/factory/Factories.java
@@ -0,0 +1,50 @@
+package reactor.alloc.factory;
+
+/**
+ * Helper class for creating object factories.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public abstract class Factories {
+
+	private static final int DEFAULT_BATCH_SIZE = 2 * 1024;
+
+	protected Factories() {
+	}
+
+	/**
+	 * Create a {@link reactor.alloc.factory.BatchFactorySupplier} that supplies instances of the given type, which
+	 * are created by invoked their no-arg constructor. An error will occur at creation if the type to be pooled has no
+	 * zero-arg constructor (it can be private or protected; it doesn't have to be public, but it must exist).
+	 *
+	 * @param type
+	 * 		the type to be pooled
+	 * @param <T>
+	 * 		the type of the pooled objects
+	 *
+	 * @return a {@link reactor.alloc.factory.BatchFactorySupplier} to supply instances of {@literal type}.
+	 */
+	public static <T> BatchFactorySupplier<T> create(Class<T> type) {
+		return create(DEFAULT_BATCH_SIZE, type);
+	}
+
+	/**
+	 * Create a {@link reactor.alloc.factory.BatchFactorySupplier} that supplies instances of the given type, which
+	 * are created by invoked their no-arg constructor. An error will occur at creation if the type to be pooled has no
+	 * zero-arg constructor (it can be private or protected; it doesn't have to be public, but it must exist).
+	 *
+	 * @param size
+	 * 		size of the pool
+	 * @param type
+	 * 		the type to be pooled
+	 * @param <T>
+	 * 		the type of the pooled objects
+	 *
+	 * @return a {@link reactor.alloc.factory.BatchFactorySupplier} to supply instances of {@literal type}.
+	 */
+	public static <T> BatchFactorySupplier<T> create(int size, Class<T> type) {
+		return new BatchFactorySupplier<T>(size, new NoArgConstructorFactory<T>(type));
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/factory/NoArgConstructorFactory.java b/reactor-core/src/main/java/reactor/alloc/factory/NoArgConstructorFactory.java
new file mode 100644
index 0000000..283584b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/factory/NoArgConstructorFactory.java
@@ -0,0 +1,38 @@
+package reactor.alloc.factory;
+
+import reactor.function.Supplier;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * A {@link reactor.function.Supplier} implementation that simply instantiates objects
+ * using reflection from a no-arg constructor.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public class NoArgConstructorFactory<T> implements Supplier<T> {
+
+	private final Class<T>       type;
+	private final Constructor<T> ctor;
+
+	public NoArgConstructorFactory(Class<T> type) {
+		this.type = type;
+		try {
+			this.ctor = type.getConstructor();
+			this.ctor.setAccessible(true);
+		} catch(NoSuchMethodException e) {
+			throw new IllegalArgumentException(e.getMessage(), e);
+		}
+	}
+
+	@Override
+	public T get() {
+		try {
+			return ctor.newInstance();
+		} catch(Exception e) {
+			throw new RuntimeException(e.getMessage(), e);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/alloc/package-info.java b/reactor-core/src/main/java/reactor/alloc/package-info.java
new file mode 100644
index 0000000..2965fd5
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Pooling provides lower GC requirements by re-using components.
+ */
+package reactor.alloc;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/alloc/spec/RingBufferAllocatorSpec.java b/reactor-core/src/main/java/reactor/alloc/spec/RingBufferAllocatorSpec.java
new file mode 100644
index 0000000..86fe903
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/alloc/spec/RingBufferAllocatorSpec.java
@@ -0,0 +1,202 @@
+package reactor.alloc.spec;
+
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.EventHandler;
+import com.lmax.disruptor.ExceptionHandler;
+import com.lmax.disruptor.WaitStrategy;
+import com.lmax.disruptor.dsl.ProducerType;
+import reactor.alloc.Recyclable;
+import reactor.alloc.Reference;
+import reactor.alloc.RingBufferAllocator;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+import reactor.util.Assert;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Helper class for creating a {@link reactor.alloc.RingBufferAllocator}. Provides an easy way to specify the
+ * {@code EventFactory}, {@code EventHandler}, and {@code ExceptionHandler} via Reactor abstractions of {@link
+ * reactor.function.Supplier} and {@link Consumer}.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public class RingBufferAllocatorSpec<T extends Recyclable> implements Supplier<RingBufferAllocator<T>> {
+
+	private String name         = "ring-buffer-allocator";
+	private int    ringSize     = 1024;
+	private int    eventThreads = 1;
+
+	private Supplier<T>            allocator;
+	private Consumer<Reference<T>> eventHandler;
+	private Consumer<Throwable>    errorHandler;
+	private ProducerType           producerType;
+	private WaitStrategy           waitStrategy;
+	private ExecutorService        executor;
+
+	/**
+	 * Name of the {@code RingBufferAllocator}.
+	 *
+	 * @param name
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> name(String name) {
+		this.name = name;
+		return this;
+	}
+
+	/**
+	 * Size of the {@code RingBuffer} ring.
+	 *
+	 * @param ringSize
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> ringSize(int ringSize) {
+		Assert.isTrue(ringSize > 0, "Ring size must be greater than 0 (zero).");
+		this.ringSize = ringSize;
+		return this;
+	}
+
+	/**
+	 * Specify the number of threads the underlying {@link com.lmax.disruptor.RingBuffer} will use.
+	 *
+	 * @param eventThreads
+	 * 		number of threads for event handlers
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> eventThreads(int eventThreads) {
+		Assert.isTrue(eventThreads > 0, "Threads size must be 1 or greater.");
+		this.eventThreads = eventThreads;
+		return this;
+	}
+
+	/**
+	 * The {@link reactor.function.Supplier} to provide the {@code RingBuffer} with the reusable objects that are being
+	 * pooled.
+	 *
+	 * @param allocator
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> allocator(Supplier<T> allocator) {
+		this.allocator = allocator;
+		return this;
+	}
+
+	/**
+	 * The {@link reactor.function.Consumer} to be invoked with a sequence ID is published.
+	 *
+	 * @param eventHandler
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> eventHandler(Consumer<Reference<T>> eventHandler) {
+		this.eventHandler = eventHandler;
+		return this;
+	}
+
+	/**
+	 * The {@link reactor.function.Consumer} to be invoked when an error occurs.
+	 *
+	 * @param errorHandler
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> errorHandler(Consumer<Throwable> errorHandler) {
+		this.errorHandler = errorHandler;
+		return this;
+	}
+
+	/**
+	 * Specify single or multi-threaded producer.
+	 *
+	 * @param producerType
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> producerType(ProducerType producerType) {
+		this.producerType = producerType;
+		return this;
+	}
+
+	/**
+	 * Specify the {@link com.lmax.disruptor.WaitStrategy} to use (defaults to {@link
+	 * com.lmax.disruptor.BlockingWaitStrategy}).
+	 *
+	 * @param waitStrategy
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> waitStrategy(WaitStrategy waitStrategy) {
+		this.waitStrategy = waitStrategy;
+		return this;
+	}
+
+	/**
+	 * The {@link java.util.concurrent.ExecutorService} to use inside the {@link com.lmax.disruptor.RingBuffer}.
+	 *
+	 * @param executor
+	 *
+	 * @return {@code this}
+	 */
+	public RingBufferAllocatorSpec<T> executor(ExecutorService executor) {
+		this.executor = executor;
+		return this;
+	}
+
+	@Override
+	public RingBufferAllocator<T> get() {
+		Assert.notNull(allocator, "Object Supplier (allocator) cannot be null.");
+
+		if(null == producerType) {
+			producerType = ProducerType.MULTI;
+		}
+		if(null == waitStrategy) {
+			waitStrategy = new BlockingWaitStrategy();
+		}
+
+		RingBufferAllocator<T> alloc = new RingBufferAllocator<T>(
+				name,
+				ringSize,
+				allocator,
+				eventThreads,
+				(null != eventHandler ? new EventHandler<Reference<T>>() {
+					@Override
+					public void onEvent(Reference<T> ref, long sequence, boolean endOfBatch) throws Exception {
+						eventHandler.accept(ref);
+						if(ref.getReferenceCount() > 0) {
+							ref.release();
+						}
+					}
+				} : null),
+				(null != errorHandler ? new ExceptionHandler() {
+					@Override
+					public void handleEventException(Throwable ex, long sequence, Object event) {
+						errorHandler.accept(ex);
+					}
+
+					@Override
+					public void handleOnStartException(Throwable ex) {
+						errorHandler.accept(ex);
+					}
+
+					@Override
+					public void handleOnShutdownException(Throwable ex) {
+						errorHandler.accept(ex);
+					}
+				} : null),
+				producerType,
+				waitStrategy,
+				executor
+		);
+		// auto-start
+		alloc.start();
+
+		return alloc;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/convert/ConversionFailedException.java b/reactor-core/src/main/java/reactor/convert/ConversionFailedException.java
new file mode 100644
index 0000000..05eeee9
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/convert/ConversionFailedException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.convert;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ConversionFailedException extends IllegalStateException {
+
+	private static final long serialVersionUID = -8502594056926933555L;
+	private final Class<?> sourceType;
+	private final Class<?> targetType;
+
+	ConversionFailedException(Class<?> sourceType, Class<?> targetType) {
+		super(String.format("Cannot convert %s to type %s", sourceType.getName(), targetType.getName()));
+		this.sourceType = sourceType;
+		this.targetType = targetType;
+	}
+
+	ConversionFailedException(Throwable cause, Class<?> sourceType, Class<?> targetType) {
+		super(String.format("Cannot convert %s to type %s", sourceType.getName(), targetType.getName()), cause);
+		this.sourceType = sourceType;
+		this.targetType = targetType;
+	}
+
+	/**
+	 * Returns the type that could not be converted from
+	 *
+	 * @return the type that could not be converted from
+	 */
+	public Class<?> getSourceType() {
+		return sourceType;
+	}
+
+	/**
+	 * Returns the type that could not be converted to
+	 *
+	 * @return the type that could not be converted to
+	 */
+	public Class<?> getTargetType() {
+		return targetType;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/convert/Converter.java b/reactor-core/src/main/java/reactor/convert/Converter.java
new file mode 100644
index 0000000..3641433
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/convert/Converter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.convert;
+
+/**
+ * @author Stephane Maldini (smaldini)
+ * @date: 3/21/13
+ * <p/>
+ * Port when Spring org.springframework.core.convert.ConversionService
+ */
+public interface Converter {
+
+	/**
+	 * Returns true if objects of sourceType can be converted to targetType. If this method returns true, it means {@link
+	 * #convert(Object, Class)} is capable of converting an instance of sourceType to targetType.
+	 *
+	 * @param sourceType the source type to convert when (may be null if source is null)
+	 * @param targetType the target type to convert to (required)
+	 * @return true if a conversion can be performed, false if not
+	 */
+	boolean canConvert(Class<?> sourceType, Class<?> targetType);
+
+	/**
+	 * Convert the source to targetType.
+	 *
+	 * @param source     the source object to convert (may be null)
+	 * @param targetType the target type to convert to (required)
+	 * @return the converted object, an instance of targetType
+	 */
+	<T> T convert(Object source, Class<T> targetType);
+
+}
diff --git a/reactor-core/src/main/java/reactor/convert/DelegatingConverter.java b/reactor-core/src/main/java/reactor/convert/DelegatingConverter.java
new file mode 100644
index 0000000..e8eb70d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/convert/DelegatingConverter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.convert;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A converter that delegates to one or more converters. The delegates are tried in
+ * order until one is found that {@link Converter#canConvert can perform} the
+ * conversion.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class DelegatingConverter implements Converter {
+
+	private final List<Converter> delegateConverters;
+
+	/**
+	 * Creates a new {@code DelegatingConverter} that will delegate to the
+	 * given list of {@code delegateConverters}.
+	 *
+	 * @param delegateConverters The converters to delegate to
+	 */
+	public DelegatingConverter(List<Converter> delegateConverters) {
+		this.delegateConverters = new ArrayList<Converter>(delegateConverters);
+	}
+
+	/**
+	 * Creates a new {@code DelegatingConverter} that will delegate to the
+	 * given list of {@code delegateConverters}.
+	 *
+	 * @param delegateConverters The converters to delegate to
+	 */
+	public DelegatingConverter(Converter... delegateConverters) {
+		this.delegateConverters = Arrays.asList(delegateConverters);
+	}
+
+	@Override
+	public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
+		if (delegateConverters != null && !delegateConverters.isEmpty()) {
+			for (Converter c : delegateConverters) {
+				if (c.canConvert(sourceType, targetType)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public <T> T convert(Object source, Class<T> targetType) {
+		if (null == source) {
+			return null;
+		}
+		for (Converter c : delegateConverters) {
+			if (c.canConvert(source.getClass(), targetType)) {
+				return c.convert(source, targetType);
+			}
+		}
+		throw new ConversionFailedException(source.getClass(), targetType);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/convert/StandardConverters.java b/reactor-core/src/main/java/reactor/convert/StandardConverters.java
new file mode 100644
index 0000000..ba01c6e
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/convert/StandardConverters.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.convert;
+
+import reactor.util.Assert;
+
+import java.lang.reflect.Constructor;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class StandardConverters {
+
+	private static final Map<Class<?>, Constructor<?>> CTORS       = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<?>>());
+	private static final Map<Class<?>, Class<?>>       CTOR_PARAMS = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>>());
+
+	/**
+	 * A {@link DelegatingConverter} that will delegate to a {@link StringToNumberConverter},
+	 * a {@link ConstructorParameterConverter} and a {@link ToStringConverter} in that order.
+	 */
+	public static final Converter CONVERTERS = new DelegatingConverter(
+			StringToNumberConverter.INSTANCE,
+			ConstructorParameterConverter.INSTANCE,
+			ToStringConverter.INSTANCE
+	);
+
+	protected StandardConverters() {
+	}
+
+	private static Class<?> findFirstCtorParam(Class<?> type) {
+		Class<?> paramType = CTOR_PARAMS.get(type);
+		if (null != paramType) {
+			return paramType;
+		}
+
+		for (Constructor<?> ctor : type.getConstructors()) {
+			if (ctor.getParameterTypes().length == 1) {
+				paramType = ctor.getParameterTypes()[0];
+				CTOR_PARAMS.put(type, paramType);
+				CTORS.put(type, ctor);
+				return paramType;
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * A {@link Converter} that will convert to the target type by creating a new instance
+	 * of the target type using the source, or a conversion of the source, as the argument
+	 * that's passed to the target type's constructor.
+	 * <p>
+	 * For conversion to be possible the target type must have a constructor that requires
+	 * a single argument. In the event of the target type having multiple single-argument
+	 * constructors, the first single-argument constructor that's found will be used.
+	 *
+	 * @author Jon Brisbin
+	 *
+	 */
+	public enum ConstructorParameterConverter implements Converter {
+
+		/**
+		 * The instance of this converter
+		 */
+		INSTANCE;
+
+		@Override
+		public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
+			Class<?> paramType = findFirstCtorParam(targetType);
+			return null != paramType && (paramType.isAssignableFrom(sourceType) || CONVERTERS.canConvert(sourceType, paramType));
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public <T> T convert(Object source, Class<T> targetType) {
+			Assert.notNull(source, "Source object cannot be null.");
+
+			Class<?> paramType = findFirstCtorParam(targetType);
+			if (!paramType.isAssignableFrom(source.getClass())) {
+				source = CONVERTERS.convert(source, paramType);
+			}
+
+			try {
+				return (T) CTORS.get(targetType).newInstance(source);
+			} catch (Throwable t) {
+				throw new ConversionFailedException(t, source.getClass(), targetType);
+			}
+		}
+	}
+
+	/**
+	 * A {@link Converter} that will convert a {@link String} to a {@link Number}. The
+	 * following {@code Number} subclasses are supported:
+	 *
+	 * <ul>
+	 *	<li>{@link AtomicInteger}</li>
+	 *	<li>{@link AtomicLong}</li>
+	 *	<li>{@link BigInteger}</li>
+	 * 	<li>{@link Byte}</li>
+	 *	<li>{@link Double}</li>
+	 *	<li>{@link Float}</li>
+	 * 	<li>{@link Integer}</li>
+	 *	<li>{@link Long}</li>
+	 *	<li>{@link Short}</li>
+	 * </ul>
+	 *
+	 * @author Jon Brisbin
+	 */
+	public enum StringToNumberConverter implements Converter {
+
+		/**
+		 * The instance of this converter
+		 */
+		INSTANCE;
+
+		@Override
+		public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
+			return String.class == sourceType && Number.class.isAssignableFrom(targetType);
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public <T> T convert(Object source, Class<T> targetType) {
+			Assert.notNull(source, "Source cannot be null when converting to a Number.");
+
+			if (Integer.class == targetType) {
+				return (T) Integer.valueOf(source.toString());
+			} else if (Byte.class == targetType) {
+				return (T) Byte.valueOf(source.toString());
+			} else if (Long.class == targetType) {
+				return (T) Long.valueOf(source.toString());
+			} else if (Short.class == targetType) {
+				return (T) Short.valueOf(source.toString());
+			} else if (Float.class == targetType) {
+				return (T) Float.valueOf(source.toString());
+			} else if (Double.class == targetType) {
+				return (T) Double.valueOf(source.toString());
+			} else if (BigInteger.class == targetType) {
+				return (T) BigInteger.valueOf(Long.valueOf(source.toString()));
+			} else if (AtomicInteger.class == targetType) {
+				return (T) new AtomicInteger(Integer.valueOf(source.toString()));
+			} else if (AtomicLong.class == targetType) {
+				return (T) new AtomicLong(Long.valueOf(source.toString()));
+			}
+
+			throw new ConversionFailedException(source.getClass(), targetType);
+		}
+
+	}
+
+	/**
+	 * A {@link Converter} that will convert any Object to a String by
+	 * calling its {@code toString} method.
+	 *
+	 * @author Jon Brisbin
+	 *
+	 */
+	public enum ToStringConverter implements Converter {
+
+		/**
+		 * The instance of this converter
+		 */
+		INSTANCE;
+
+		@Override
+		public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
+			return String.class == targetType;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public <T> T convert(Object source, Class<T> targetType) {
+			return (T) String.format("%s", source);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/convert/package-info.java b/reactor-core/src/main/java/reactor/convert/package-info.java
new file mode 100644
index 0000000..89856ca
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/convert/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Conversion from one datatype to another.
+ */
+package reactor.convert;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/Environment.java b/reactor-core/src/main/java/reactor/core/Environment.java
new file mode 100644
index 0000000..efe5940
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/Environment.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core;
+
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.dsl.ProducerType;
+import reactor.convert.StandardConverters;
+import reactor.core.configuration.*;
+import reactor.event.dispatch.*;
+import reactor.filter.Filter;
+import reactor.filter.RoundRobinFilter;
+import reactor.timer.SimpleHashWheelTimer;
+import reactor.timer.Timer;
+import reactor.util.LinkedMultiValueMap;
+import reactor.util.MultiValueMap;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+public class Environment implements Iterable<Map.Entry<String, List<Dispatcher>>> {
+
+	/**
+	 * The name of the default event loop dispatcher
+	 */
+	public static final String EVENT_LOOP = "eventLoop";
+
+	/**
+	 * The name of the default ring buffer dispatcher
+	 */
+	public static final String RING_BUFFER = "ringBuffer";
+
+	/**
+	 * The name of the default thread pool dispatcher
+	 */
+	public static final String THREAD_POOL = "threadPoolExecutor";
+
+	/**
+	 * The name of the default work queue dispatcher
+	 */
+	public static final String WORK_QUEUE = "workQueue";
+
+	/**
+	 * The number of processors available to the runtime
+	 *
+	 * @see Runtime#availableProcessors()
+	 */
+	public static final int PROCESSORS = Runtime.getRuntime().availableProcessors();
+
+	private static final String DEFAULT_DISPATCHER_NAME = "__default-dispatcher";
+	private static final String SYNC_DISPATCHER_NAME    = "sync";
+
+	private final Properties env;
+
+	private final AtomicReference<Timer>   timer            = new AtomicReference<Timer>();
+	private final AtomicReference<Reactor> rootReactor      = new AtomicReference<Reactor>();
+	private final Object                   monitor          = new Object();
+	private final Filter                   dispatcherFilter = new RoundRobinFilter();
+
+	private final MultiValueMap<String, Dispatcher> dispatchers;
+	private final String                            defaultDispatcher;
+
+	/**
+	 * Creates a new Environment that will use a {@link PropertiesConfigurationReader} to obtain its initial
+	 * configuration. The configuration will be read from the classpath at the location {@code
+	 * META-INF/reactor/default.properties}.
+	 */
+	public Environment() {
+		this(Collections.<String, List<Dispatcher>>emptyMap(), new PropertiesConfigurationReader());
+	}
+
+	/**
+	 * Creates a new Environment that will use the given {@code configurationReader} to obtain its initial configuration.
+	 *
+	 * @param configurationReader
+	 * 		The configuration reader to use to obtain initial configuration
+	 */
+	public Environment(ConfigurationReader configurationReader) {
+		this(Collections.<String, List<Dispatcher>>emptyMap(), configurationReader);
+	}
+
+	/**
+	 * Creates a new Environment that will contain the given {@code dispatchers}, will use the given {@code
+	 * configurationReader} to obtain additional configuration.
+	 *
+	 * @param dispatchers
+	 * 		The dispatchers to add include in the Environment
+	 * @param configurationReader
+	 * 		The configuration reader to use to obtain additional configuration
+	 */
+	public Environment(Map<String, List<Dispatcher>> dispatchers, ConfigurationReader configurationReader) {
+		this.dispatchers = new LinkedMultiValueMap<String, Dispatcher>(dispatchers);
+
+		ReactorConfiguration configuration = configurationReader.read();
+		defaultDispatcher = configuration.getDefaultDispatcherName() != null ? configuration.getDefaultDispatcherName() :
+				DEFAULT_DISPATCHER_NAME;
+		env = configuration.getAdditionalProperties();
+
+		for (DispatcherConfiguration dispatcherConfiguration : configuration.getDispatcherConfigurations()) {
+			if (DispatcherType.EVENT_LOOP == dispatcherConfiguration.getType()) {
+				int size = getSize(dispatcherConfiguration, 0);
+				for (int i = 0; i < size; i++) {
+					addDispatcher(dispatcherConfiguration.getName(), createBlockingQueueDispatcher(dispatcherConfiguration));
+				}
+			} else if (DispatcherType.RING_BUFFER == dispatcherConfiguration.getType()) {
+				addDispatcher(dispatcherConfiguration.getName(), createRingBufferDispatcher(dispatcherConfiguration));
+			} else if (DispatcherType.SYNCHRONOUS == dispatcherConfiguration.getType()) {
+				addDispatcher(dispatcherConfiguration.getName(), new SynchronousDispatcher());
+			} else if (DispatcherType.THREAD_POOL_EXECUTOR == dispatcherConfiguration.getType()) {
+				addDispatcher(dispatcherConfiguration.getName(), createThreadPoolExecutorDispatcher(dispatcherConfiguration));
+			} else if (DispatcherType.WORK_QUEUE == dispatcherConfiguration.getType()) {
+				addDispatcher(dispatcherConfiguration.getName(), createWorkQueueDispatcher(dispatcherConfiguration));
+			}
+		}
+
+		addDispatcher(SYNC_DISPATCHER_NAME, new SynchronousDispatcher());
+	}
+
+	private ThreadPoolExecutorDispatcher createThreadPoolExecutorDispatcher(DispatcherConfiguration
+			                                                                        dispatcherConfiguration) {
+		int size = getSize(dispatcherConfiguration, 0);
+		int backlog = getBacklog(dispatcherConfiguration, 128);
+
+		return new ThreadPoolExecutorDispatcher(size,
+		                                        backlog,
+		                                        dispatcherConfiguration.getName());
+	}
+
+	private WorkQueueDispatcher createWorkQueueDispatcher(DispatcherConfiguration dispatcherConfiguration) {
+		int size = getSize(dispatcherConfiguration, 0);
+		int backlog = getBacklog(dispatcherConfiguration, 16384);
+
+		return new WorkQueueDispatcher("workQueueDispatcher",
+		                               size,
+		                               backlog,
+		                               null);
+	}
+
+	private RingBufferDispatcher createRingBufferDispatcher(DispatcherConfiguration dispatcherConfiguration) {
+		int backlog = getBacklog(dispatcherConfiguration, 1024);
+		return new RingBufferDispatcher(dispatcherConfiguration.getName(),
+		                                backlog,
+		                                null,
+		                                ProducerType.MULTI,
+		                                new BlockingWaitStrategy());
+	}
+
+	private EventLoopDispatcher createBlockingQueueDispatcher(DispatcherConfiguration dispatcherConfiguration) {
+		int backlog = getBacklog(dispatcherConfiguration, 128);
+
+		return new EventLoopDispatcher(dispatcherConfiguration.getName(), backlog);
+	}
+
+	private int getBacklog(DispatcherConfiguration dispatcherConfiguration, int defaultBacklog) {
+		Integer backlog = dispatcherConfiguration.getBacklog();
+		if (null == backlog) {
+			backlog = defaultBacklog;
+		}
+		return backlog;
+	}
+
+	private int getSize(DispatcherConfiguration dispatcherConfiguration, int defaultSize) {
+		Integer size = dispatcherConfiguration.getSize();
+		if (null == size) {
+			size = defaultSize;
+		}
+		if (size < 1) {
+			size = PROCESSORS;
+		}
+		return size;
+	}
+
+	/**
+	 * Gets the property with the given {@code key}. If the property does not exist {@code defaultValue} will be
+	 * returned.
+	 *
+	 * @param key
+	 * 		The property key
+	 * @param defaultValue
+	 * 		The value to return if the property does not exist
+	 *
+	 * @return The value for the property
+	 */
+	public String getProperty(String key, String defaultValue) {
+		return env.getProperty(key, defaultValue);
+	}
+
+	/**
+	 * Gets the property with the given {@code key}, converting it to the required {@code type} using the {@link
+	 * StandardConverters#CONVERTERS standard converters}. fF the property does not exist {@code defaultValue} will be
+	 * returned.
+	 *
+	 * @param key
+	 * 		The property key
+	 * @param type
+	 * 		The type to convert the property to
+	 * @param defaultValue
+	 * 		The value to return if the property does not exist
+	 *
+	 * @return The converted value for the property
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> T getProperty(String key, Class<T> type, T defaultValue) {
+		Object val = env.getProperty(key);
+		if (null == val) {
+			return defaultValue;
+		}
+		if (!type.isAssignableFrom(val.getClass()) && StandardConverters.CONVERTERS.canConvert(String.class, type)) {
+			return StandardConverters.CONVERTERS.convert(val, type);
+		} else {
+			return (T) val;
+		}
+	}
+
+	/**
+	 * Returns the default dispatcher for this environment. By default, when a {@link PropertiesConfigurationReader} is
+	 * being used. This default dispatcher is specified by the value of the {@code reactor.dispatchers.default} property.
+	 *
+	 * @return The default dispatcher
+	 */
+	public Dispatcher getDefaultDispatcher() {
+		return getDispatcher(defaultDispatcher);
+	}
+
+	/**
+	 * Returns the dispatcher with the given {@code name}.
+	 *
+	 * @param name
+	 * 		The name of the dispatcher
+	 *
+	 * @return The matching dispatcher, never {@code null}.
+	 * @throws IllegalArgumentException
+	 * 		if the dispatcher does not exist
+	 */
+	public Dispatcher getDispatcher(String name) {
+		synchronized (monitor) {
+			List<Dispatcher> filteredDispatchers = Collections.emptyList();
+			List<Dispatcher> dispatchers = this.dispatchers.get(name);
+			if (dispatchers != null) {
+				filteredDispatchers = this.dispatcherFilter.filter(dispatchers, name);
+			}
+			if (filteredDispatchers.isEmpty()) {
+				throw new IllegalArgumentException("No Dispatcher found for name '" + name + "'");
+			} else {
+				return filteredDispatchers.get(0);
+			}
+		}
+	}
+
+	/**
+	 * Adds the {@code dispatcher} to the environment, storing it using the given {@code name}.
+	 *
+	 * @param name
+	 * 		The name of the dispatcher
+	 * @param dispatcher
+	 * 		The dispatcher
+	 *
+	 * @return This Environment
+	 */
+	public Environment addDispatcher(String name, Dispatcher dispatcher) {
+		synchronized (monitor) {
+			this.dispatchers.add(name, dispatcher);
+			if (name.equals(defaultDispatcher)) {
+				this.dispatchers.add(DEFAULT_DISPATCHER_NAME, dispatcher);
+			}
+		}
+		return this;
+	}
+
+	/**
+	 * Removes the Dispatcher, stored using the given {@code name} from the environment.
+	 *
+	 * @param name
+	 * 		The name of the dispatcher
+	 *
+	 * @return This Environment
+	 */
+	public Environment removeDispatcher(String name) {
+		synchronized (monitor) {
+			dispatchers.remove(name);
+		}
+		return this;
+	}
+
+	/**
+	 * Returns this environments root Reactor, creating it if necessary. The Reactor will use the environment default
+	 * dispatcher.
+	 *
+	 * @return The root reactor
+	 * @see Environment#getDefaultDispatcher()
+	 */
+	public Reactor getRootReactor() {
+		if (null == rootReactor.get()) {
+			synchronized (rootReactor) {
+				rootReactor.compareAndSet(null, new Reactor(getDefaultDispatcher()));
+			}
+		}
+		return rootReactor.get();
+	}
+
+	/**
+	 * Get the {@code Environment}-wide {@link reactor.timer.SimpleHashWheelTimer}.
+	 *
+	 * @return the timer.
+	 */
+	public Timer getRootTimer() {
+		if (null == timer.get()) {
+			synchronized (timer) {
+				SimpleHashWheelTimer t = new SimpleHashWheelTimer();
+				if (!timer.compareAndSet(null, t)) {
+					t.cancel();
+				}
+			}
+		}
+		return timer.get();
+	}
+
+	/**
+	 * Shuts down this Environment, causing all of its {@link Dispatcher Dispatchers} to be shut down.
+	 *
+	 * @see Dispatcher#shutdown
+	 */
+	public void shutdown() {
+		List<Dispatcher> dispatchers = new ArrayList<Dispatcher>();
+		synchronized (monitor) {
+			for (Map.Entry<String, List<Dispatcher>> entry : this.dispatchers.entrySet()) {
+				dispatchers.addAll(entry.getValue());
+			}
+		}
+		for (Dispatcher dispatcher : dispatchers) {
+			dispatcher.shutdown();
+		}
+		if (null != timer.get()) {
+			timer.get().cancel();
+		}
+	}
+
+	@Override
+	public Iterator<Map.Entry<String, List<Dispatcher>>> iterator() {
+		return this.dispatchers.entrySet().iterator();
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/Observable.java b/reactor-core/src/main/java/reactor/core/Observable.java
new file mode 100644
index 0000000..3759003
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/Observable.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core;
+
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+import reactor.event.selector.Selector;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.function.Supplier;
+
+/**
+ * Basic unit of event handling in Reactor.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+public interface Observable {
+
+	/**
+	 * Are there any {@link Registration}s with {@link Selector Selectors} that match the given {@code key}.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 *
+	 * @return {@literal true} if there are any matching {@literal Registration}s, {@literal false} otherwise
+	 */
+	boolean respondsToKey(Object key);
+
+	/**
+	 * Register a {@link reactor.function.Consumer} to be triggered when a notification matches the given {@link
+	 * Selector}.
+	 *
+	 * @param sel
+	 * 		The {@literal Selector} to be used for matching
+	 * @param consumer
+	 * 		The {@literal Consumer} to be triggered
+	 * @param <E>
+	 * 		The type of the {@link reactor.event.Event}
+	 *
+	 * @return A {@link Registration} object that allows the caller to interact with the given mapping
+	 */
+	<E extends Event<?>> Registration<Consumer<E>> on(Selector sel, Consumer<E> consumer);
+
+	/**
+	 * Assign a {@link reactor.function.Function} to receive an {@link Event} and produce a reply of the given type.
+	 *
+	 * @param sel
+	 * 		The {@link Selector} to be used for matching
+	 * @param fn
+	 * 		The transformative {@link reactor.function.Function} to call to receive an {@link Event}
+	 * @param <E>
+	 * 		The type of the {@link Event}
+	 * @param <V>
+	 * 		The type of the response data
+	 *
+	 * @return A {@link Registration} object that allows the caller to interact with the given mapping
+	 */
+	<E extends Event<?>, V> Registration<Consumer<E>> receive(Selector sel, Function<E, V> fn);
+
+	/**
+	 * Notify this component that an {@link Event} is ready to be processed and {@link Consumer#accept accept} {@code
+	 * onComplete} after dispatching.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param ev
+	 * 		The {@literal Event}
+	 * @param onComplete
+	 * 		The callback {@link Consumer}
+	 * @param <E>
+	 * 		The type of the {@link Event}
+	 *
+	 * @return {@literal this}
+	 */
+	<E extends Event<?>> Observable notify(Object key, E ev, Consumer<E> onComplete);
+
+	/**
+	 * Notify this component that an {@link Event} is ready to be processed.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param ev
+	 * 		The {@literal Event}
+	 * @param <E>
+	 * 		The type of the {@link Event}
+	 *
+	 * @return {@literal this}
+	 */
+	<E extends Event<?>> Observable notify(Object key, E ev);
+
+	/**
+	 * Notify this component that the given {@link reactor.function.Supplier} can provide an event that's ready to be
+	 * processed.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param supplier
+	 * 		The {@link reactor.function.Supplier} that will provide the actual {@link Event}
+	 * @param <S>
+	 * 		The type of the {@link reactor.function.Supplier}
+	 *
+	 * @return {@literal this}
+	 */
+	<S extends Supplier<? extends Event<?>>> Observable notify(Object key, S supplier);
+
+
+	/**
+	 * Notify this component of the given {@link Event} and register an internal {@link Consumer} that will take the
+	 * output of a previously-registered {@link Function} and respond using the key set on the {@link Event}'s {@literal
+	 * replyTo} property.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param ev
+	 * 		The {@literal Event}
+	 * @param <E>
+	 * 		The type of the {@link Event}
+	 *
+	 * @return {@literal this}
+	 */
+	<E extends Event<?>> Observable send(Object key, E ev);
+
+	/**
+	 * Notify this component that the given {@link Supplier} will provide an {@link Event} and register an internal {@link
+	 * Consumer} that will take the output of a previously-registered {@link Function} and respond using the key set on
+	 * the {@link Event}'s {@literal replyTo} property.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param supplier
+	 * 		The {@link Supplier} that will provide the actual {@link Event} instance
+	 *
+	 * @return {@literal this}
+	 */
+	<S extends Supplier<? extends Event<?>>> Observable send(Object key, S supplier);
+
+	/**
+	 * Notify this component of the given {@link Event} and register an internal {@link Consumer} that will take the
+	 * output of a previously-registered {@link Function} and respond to the key set on the {@link Event}'s {@literal
+	 * replyTo} property and will call the {@code notify} method on the given {@link Observable}.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param ev
+	 * 		The {@literal Event}
+	 * @param replyTo
+	 * 		The {@link Observable} on which to invoke the notify method
+	 * @param <E>
+	 * 		The type of the {@link Event}
+	 *
+	 * @return {@literal this}
+	 */
+	<E extends Event<?>> Observable send(Object key, E ev, Observable replyTo);
+
+	/**
+	 * Notify this component that the given {@link Supplier} will provide an {@link Event} and register an internal {@link
+	 * Consumer} that will take the output of a previously-registered {@link Function} and respond to the key set on the
+	 * {@link Event}'s {@literal replyTo} property and will call the {@code notify} method on the given {@link
+	 * Observable}.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param supplier
+	 * 		The {@link Supplier} that will provide the actual {@link Event} instance
+	 * @param replyTo
+	 * 		The {@link Observable} on which to invoke the notify method
+	 * @param <S>
+	 * 		The type of the Supplier
+	 *
+	 * @return {@literal this}
+	 */
+	<S extends Supplier<? extends Event<?>>> Observable send(Object key, S supplier, Observable replyTo);
+
+	/**
+	 * Register the given {@link reactor.function.Consumer} on an anonymous {@link reactor.event.selector.Selector} and
+	 * set the given event's {@code replyTo} property to the corresponding anonymous key, then register the consumer to
+	 * receive replies from the {@link reactor.function.Function} assigned to handle the given key.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param ev
+	 * 		The event to notify.
+	 * @param reply
+	 * 		The consumer to register as a reply handler.
+	 * @param <REQ>
+	 * 		The type of the request event.
+	 * @param <RESP>
+	 * 		The type of the response event.
+	 *
+	 * @return {@literal this}
+	 */
+	<REQ extends Event<?>, RESP extends Event<?>> Observable sendAndReceive(Object key, REQ ev, Consumer<RESP> reply);
+
+	/**
+	 * Register the given {@link reactor.function.Consumer} on an anonymous {@link reactor.event.selector.Selector} and
+	 * set the event's {@code replyTo} property to the corresponding anonymous key, then register the consumer to receive
+	 * replies from the {@link reactor.function.Function} assigned to handle the given key.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param supplier
+	 * 		The supplier to supply the event.
+	 * @param reply
+	 * 		The consumer to register as a reply handler.
+	 * @param <REQ>
+	 * 		The type of the request event.
+	 * @param <RESP>
+	 * 		The type of the response event.
+	 * @param <S>
+	 * 		The type of the supplier.
+	 *
+	 * @return {@literal this}
+	 */
+	<REQ extends Event<?>, RESP extends Event<?>, S extends Supplier<REQ>> Observable sendAndReceive(Object key,
+	                                                                                                 S supplier,
+	                                                                                                 Consumer<RESP> reply);
+
+	/**
+	 * Notify this component that the consumers registered with a {@link Selector} that matches the {@code key} should be
+	 * triggered with a {@literal null} input argument.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 *
+	 * @return {@literal this}
+	 */
+	Observable notify(Object key);
+
+	/**
+	 * Create an optimized path for publishing notifications to the given key.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 *
+	 * @return a {@link Consumer} to invoke with the {@link Event Events} to publish
+	 */
+	<T> Consumer<Event<T>> prepare(Object key);
+
+	/**
+	 * Notify the key with all any accepted iterable group of events by the returned {@link Consumer}. The implementation
+	 * will take care of reducing the consumer selection to one per batch. The candidate consumers are selected with the
+	 * key {@param key}, possibly on each batch to refresh the result list.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 *
+	 * @return a {@link Consumer} to invoke with the {@link Event Events} to publish
+	 */
+	<T> Consumer<Iterable<Event<T>>> batchNotify(Object key);
+
+	/**
+	 * Notify the key with all any accepted iterable group of events by the returned {@link Consumer}. The implementation
+	 * will take care of reducing the consumer selection to one per batch. The candidate consumers are selected with the
+	 * key {@param key}, possibly on each batch to refresh the result list.
+	 *
+	 * @param key
+	 * 		The key to be matched by {@link Selector Selectors}
+	 * @param consumer
+	 * 		The consumer to trigger after batch completion
+	 *
+	 * @return a {@link Consumer} to invoke with the {@link Event Events} to publish
+	 */
+	<T> Consumer<Iterable<Event<T>>> batchNotify(Object key, Consumer<Void> consumer);
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/Reactor.java b/reactor-core/src/main/java/reactor/core/Reactor.java
new file mode 100644
index 0000000..81927d1
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/Reactor.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.event.Event;
+import reactor.event.dispatch.Dispatcher;
+import reactor.event.dispatch.SynchronousDispatcher;
+import reactor.event.registry.CachingRegistry;
+import reactor.event.registry.Registration;
+import reactor.event.registry.Registry;
+import reactor.event.routing.ArgumentConvertingConsumerInvoker;
+import reactor.event.routing.ConsumerFilteringEventRouter;
+import reactor.event.routing.EventRouter;
+import reactor.event.selector.ClassSelector;
+import reactor.event.selector.Selector;
+import reactor.event.selector.Selectors;
+import reactor.filter.PassThroughFilter;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.function.Supplier;
+import reactor.function.support.SingleUseConsumer;
+import reactor.util.Assert;
+import reactor.util.UUIDUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * A reactor is an event gateway that allows other components to register {@link Event} {@link Consumer}s that can
+ * subsequently be notified of events. A consumer is typically registered with a {@link Selector} which, by matching on
+ * the notification key, governs which events the consumer will receive. </p> When a {@literal Reactor} is notified of
+ * an {@link Event}, a task is dispatched using the reactor's {@link Dispatcher} which causes it to be executed on a
+ * thread based on the implementation of the {@link Dispatcher} being used.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+ at SuppressWarnings({"unchecked", "rawtypes"})
+public class Reactor implements Observable {
+
+	private static final EventRouter DEFAULT_EVENT_ROUTER = new ConsumerFilteringEventRouter(
+			new PassThroughFilter(), new ArgumentConvertingConsumerInvoker(null)
+	);
+
+	private final Dispatcher                             dispatcher;
+	private final Registry<Consumer<? extends Event<?>>> consumerRegistry;
+	private final EventRouter                            eventRouter;
+	private final Consumer<Throwable>                    dispatchErrorHandler;
+	private final Consumer<Throwable>                    uncaughtErrorHandler;
+
+	private volatile UUID id;
+
+	/**
+	 * Create a new {@literal Reactor} that uses the given {@link Dispatcher}. The reactor will use a default {@link
+	 * EventRouter} that broadcast events to all of the registered consumers that {@link Selector#matches(Object) match}
+	 * the notification key and does not perform any type conversion.
+	 *
+	 * @param dispatcher
+	 * 		The {@link Dispatcher} to use. May be {@code null} in which case a new {@link SynchronousDispatcher} is used
+	 */
+	public Reactor(@Nullable Dispatcher dispatcher) {
+		this(dispatcher, null);
+	}
+
+	/**
+	 * Create a new {@literal Reactor} that uses the given {@link Dispatcher}. The reactor will use a default {@link
+	 * CachingRegistry}.
+	 *
+	 * @param dispatcher
+	 * 		The {@link Dispatcher} to use. May be {@code null} in which case a new synchronous  dispatcher is used.
+	 * @param eventRouter
+	 * 		The {@link EventRouter} used to route events to {@link Consumer Consumers}. May be {@code null} in which case the
+	 * 		default event router that broadcasts events to all of the registered consumers that {@link
+	 * 		Selector#matches(Object) match} the notification key and does not perform any type conversion will be used.
+	 */
+	public Reactor(@Nullable Dispatcher dispatcher,
+	               @Nullable EventRouter eventRouter) {
+		this(dispatcher, eventRouter, null, null);
+	}
+
+	public Reactor(@Nullable Dispatcher dispatcher,
+	               @Nullable EventRouter eventRouter,
+	               @Nullable Consumer<Throwable> dispatchErrorHandler,
+	               @Nullable final Consumer<Throwable> uncaughtErrorHandler) {
+		this(new CachingRegistry<Consumer<? extends Event<?>>>(),
+		     dispatcher,
+		     eventRouter,
+		     dispatchErrorHandler,
+		     uncaughtErrorHandler);
+	}
+
+	/**
+	 * Create a new {@literal Reactor} that uses the given {@code dispatacher} and {@code eventRouter}.
+	 *
+	 * @param dispatcher
+	 * 		The {@link Dispatcher} to use. May be {@code null} in which case a new synchronous  dispatcher is used.
+	 * @param eventRouter
+	 * 		The {@link EventRouter} used to route events to {@link Consumer Consumers}. May be {@code null} in which case the
+	 * 		default event router that broadcasts events to all of the registered consumers that {@link
+	 * 		Selector#matches(Object) match} the notification key and does not perform any type conversion will be used.
+	 * @param consumerRegistry
+	 * 		The {@link Registry} to be used to match {@link Selector} and dispatch to {@link Consumer}.
+	 */
+	public Reactor(@Nonnull Registry<Consumer<? extends Event<?>>> consumerRegistry,
+	               @Nullable Dispatcher dispatcher,
+	               @Nullable EventRouter eventRouter,
+	               @Nullable Consumer<Throwable> dispatchErrorHandler,
+	               @Nullable Consumer<Throwable> uncaughtErrorHandler) {
+		Assert.notNull(consumerRegistry, "Consumer Registry cannot be null.");
+		this.consumerRegistry = consumerRegistry;
+		this.dispatcher = (null == dispatcher ? new SynchronousDispatcher() : dispatcher);
+		this.eventRouter = (null == eventRouter ? DEFAULT_EVENT_ROUTER : eventRouter);
+		if (null == dispatchErrorHandler) {
+			this.dispatchErrorHandler = new Consumer<Throwable>() {
+				@Override
+				public void accept(Throwable t) {
+					Class<? extends Throwable> type = t.getClass();
+					Reactor.this.eventRouter.route(type,
+					                               Event.wrap(t).setKey(type),
+					                               Reactor.this.consumerRegistry.select(type),
+					                               null,
+					                               null);
+				}
+			};
+		} else {
+			this.dispatchErrorHandler = dispatchErrorHandler;
+		}
+		this.uncaughtErrorHandler = uncaughtErrorHandler;
+
+		this.on(new ClassSelector(Throwable.class), new Consumer<Event<Throwable>>() {
+			Logger log;
+
+			@Override
+			public void accept(Event<Throwable> ev) {
+				if (null == Reactor.this.uncaughtErrorHandler) {
+					if (null == log) {
+						log = LoggerFactory.getLogger(Reactor.class);
+					}
+					log.error(ev.getData().getMessage(), ev.getData());
+				} else {
+					Reactor.this.uncaughtErrorHandler.accept(ev.getData());
+				}
+			}
+		});
+	}
+
+	/**
+	 * Get the unique, time-used {@link UUID} of this {@literal Reactor}.
+	 *
+	 * @return The {@link UUID} of this {@literal Reactor}.
+	 */
+	public synchronized UUID getId() {
+		if (null == id) {
+			id = UUIDUtils.create();
+		}
+		return id;
+	}
+
+	/**
+	 * Get the {@link Registry} is use to maintain the {@link Consumer}s currently listening for events on this {@literal
+	 * Reactor}.
+	 *
+	 * @return The {@link Registry} in use.
+	 */
+	public Registry<Consumer<? extends Event<?>>> getConsumerRegistry() {
+		return consumerRegistry;
+	}
+
+	/**
+	 * Get the {@link Dispatcher} currently in use.
+	 *
+	 * @return The {@link Dispatcher}.
+	 */
+	public Dispatcher getDispatcher() {
+		return dispatcher;
+	}
+
+	/**
+	 * Get the {@link EventRouter} used to route events to {@link Consumer Consumers}.
+	 *
+	 * @return The {@link EventRouter}.
+	 */
+	public EventRouter getEventRouter() {
+		return eventRouter;
+	}
+
+	public Consumer<Throwable> getDispatchErrorHandler() {
+		return dispatchErrorHandler;
+	}
+
+	public Consumer<Throwable> getUncaughtErrorHandler() {
+		return uncaughtErrorHandler;
+	}
+
+	@Override
+	public boolean respondsToKey(Object key) {
+		for (Registration<?> reg : consumerRegistry.select(key)) {
+			if (!reg.isCancelled()) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public <E extends Event<?>> Registration<Consumer<E>> on(Selector selector, final Consumer<E> consumer) {
+		Assert.notNull(selector, "Selector cannot be null.");
+		Assert.notNull(consumer, "Consumer cannot be null.");
+		Registration<Consumer<E>> reg = consumerRegistry.register(selector, consumer);
+		return reg;
+	}
+
+	@Override
+	public <E extends Event<?>, V> Registration<Consumer<E>> receive(Selector sel, Function<E, V> fn) {
+		return on(sel, new ReplyToConsumer<E, V>(fn));
+	}
+
+	@Override
+	public <E extends Event<?>> Reactor notify(Object key, E ev, Consumer<E> onComplete) {
+		Assert.notNull(key, "Key cannot be null.");
+		Assert.notNull(ev, "Event cannot be null.");
+		ev.setKey(key);
+		dispatcher.dispatch(key, ev, consumerRegistry, dispatchErrorHandler, eventRouter, onComplete);
+
+		return this;
+	}
+
+	@Override
+	public <E extends Event<?>> Reactor notify(Object key, E ev) {
+		return notify(key, ev, null);
+	}
+
+	@Override
+	public <S extends Supplier<? extends Event<?>>> Reactor notify(Object key, S supplier) {
+		return notify(key, supplier.get(), null);
+	}
+
+	@Override
+	public Reactor notify(Object key) {
+		return notify(key, new Event<Void>(Void.class), null);
+	}
+
+	@Override
+	public <E extends Event<?>> Reactor send(Object key, E ev) {
+		return notify(key, new ReplyToEvent(ev, this));
+	}
+
+	@Override
+	public <S extends Supplier<? extends Event<?>>> Reactor send(Object key, S supplier) {
+		return notify(key, new ReplyToEvent(supplier.get(), this));
+	}
+
+	@Override
+	public <E extends Event<?>> Reactor send(Object key, E ev, Observable replyTo) {
+		return notify(key, new ReplyToEvent(ev, replyTo));
+	}
+
+	@Override
+	public <S extends Supplier<? extends Event<?>>> Reactor send(Object key, S supplier, Observable replyTo) {
+		return notify(key, new ReplyToEvent(supplier.get(), replyTo));
+	}
+
+	@Override
+	public <REQ extends Event<?>, RESP extends Event<?>> Reactor sendAndReceive(Object key,
+	                                                                            REQ ev,
+	                                                                            Consumer<RESP> reply) {
+		Selector sel = Selectors.anonymous();
+		on(sel, new SingleUseConsumer<RESP>(reply)).cancelAfterUse();
+		notify(key, ev.setReplyTo(sel.getObject()));
+		return this;
+	}
+
+	@Override
+	public <REQ extends Event<?>, RESP extends Event<?>, S extends Supplier<REQ>> Reactor sendAndReceive(Object key,
+	                                                                                                     S supplier,
+	                                                                                                     Consumer<RESP> reply) {
+		return sendAndReceive(key, supplier.get(), reply);
+	}
+
+	@Override
+	public <T> Consumer<Event<T>> prepare(final Object key) {
+		return new Consumer<Event<T>>() {
+			final List<Registration<? extends Consumer<? extends Event<?>>>> regs = consumerRegistry.select(key);
+			final int size = regs.size();
+
+			@Override
+			public void accept(Event<T> ev) {
+				for (int i = 0; i < size; i++) {
+					Registration<Consumer<Event<?>>> reg = (Registration<Consumer<Event<?>>>) regs.get(i);
+					dispatcher.dispatch(ev.setKey(key), eventRouter, reg.getObject(), dispatchErrorHandler);
+				}
+			}
+		};
+	}
+
+	@Override
+	public <T> Consumer<Iterable<Event<T>>> batchNotify(final Object key) {
+		return batchNotify(key, null);
+	}
+
+	@Override
+	public <T> Consumer<Iterable<Event<T>>> batchNotify(final Object key, final Consumer<Void> completeConsumer) {
+		return new Consumer<Iterable<Event<T>>>() {
+			final Consumer<Event<Iterable<Event<T>>>> batchConsumer = new Consumer<Event<Iterable<Event<T>>>>() {
+				@Override
+				public void accept(Event<Iterable<Event<T>>> event) {
+					List<Registration<? extends Consumer<? extends Event<?>>>> regs = consumerRegistry.select(key);
+					for (Event<T> batchedEvent : event.getData()) {
+						for (Registration<? extends Consumer<? extends Event<?>>> registration : regs) {
+							eventRouter.route(null, batchedEvent, null, registration.getObject(), dispatchErrorHandler);
+						}
+					}
+					if (completeConsumer != null) {
+						completeConsumer.accept(null);
+					}
+				}
+			};
+
+			@Override
+			public void accept(Iterable<Event<T>> evs) {
+				dispatcher.dispatch(null, Event.wrap(evs), null, dispatchErrorHandler, eventRouter, batchConsumer);
+			}
+		};
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o) {
+			return true;
+		}
+		if (o == null || getClass() != o.getClass()) {
+			return false;
+		}
+
+		return hashCode() == o.hashCode();
+
+	}
+
+	/**
+	 * Schedule an arbitrary {@link reactor.function.Consumer} to be executed on the current Reactor  {@link
+	 * reactor.event.dispatch.Dispatcher}, passing the given {@param data}.
+	 *
+	 * @param consumer
+	 * 		The {@link reactor.function.Consumer} to invoke.
+	 * @param data
+	 * 		The data to pass to the consumer.
+	 * @param <T>
+	 * 		The type of the data.
+	 */
+	public <T> void schedule(final Consumer<T> consumer, final T data) {
+		dispatcher.dispatch(null, null, null, dispatchErrorHandler, eventRouter, new Consumer<Event<?>>() {
+			@Override
+			public void accept(Event<?> event) {
+				consumer.accept(data);
+			}
+		});
+	}
+
+	public static class ReplyToEvent<T> extends Event<T> {
+		private static final long serialVersionUID = 1937884784799135647L;
+		private final Observable replyToObservable;
+
+		private ReplyToEvent(Headers headers, T data, Object replyTo,
+		                     Observable replyToObservable,
+		                     Consumer<Throwable> errorConsumer) {
+			super(headers, data, errorConsumer);
+			setReplyTo(replyTo);
+			this.replyToObservable = replyToObservable;
+		}
+
+		private ReplyToEvent(Event<T> delegate, Observable replyToObservable) {
+			this(delegate.getHeaders(), delegate.getData(), delegate.getReplyTo(), replyToObservable,
+			     delegate.getErrorConsumer());
+		}
+
+		@Override
+		public <X> Event<X> copy(X data) {
+			return new ReplyToEvent<X>(getHeaders(), data, getReplyTo(), replyToObservable, getErrorConsumer());
+		}
+
+		public Observable getReplyToObservable() {
+			return replyToObservable;
+		}
+	}
+
+	public class ReplyToConsumer<E extends Event<?>, V> implements Consumer<E> {
+		private final Function<E, V> fn;
+
+		private ReplyToConsumer(Function<E, V> fn) {
+			this.fn = fn;
+		}
+
+		@Override
+		public void accept(E ev) {
+			Observable replyToObservable = Reactor.this;
+
+			if (ReplyToEvent.class.isAssignableFrom(ev.getClass())) {
+				Observable o = ((ReplyToEvent<?>) ev).getReplyToObservable();
+				if (null != o) {
+					replyToObservable = o;
+				}
+			}
+
+			try {
+				V reply = fn.apply(ev);
+
+				Event<?> replyEv;
+				if (null == reply) {
+					replyEv = new Event<Void>(Void.class);
+				} else {
+					replyEv = (Event.class.isAssignableFrom(reply.getClass()) ? (Event<?>) reply : Event.wrap(reply));
+				}
+
+				replyToObservable.notify(ev.getReplyTo(), replyEv);
+			} catch (Throwable x) {
+				replyToObservable.notify(x.getClass(), Event.wrap(x));
+			}
+		}
+
+		public Function<E, V> getDelegate() {
+			return fn;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/Action.java b/reactor-core/src/main/java/reactor/core/action/Action.java
new file mode 100644
index 0000000..73ca83c
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/Action.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Consumer;
+
+/**
+ * Base class for all Composable actions such as map, reduce, filter...
+ *
+ * @param <T>
+ * 		The type of the values
+ *
+ * @author Stephane Maldini
+ */
+public abstract class Action<T> implements Consumer<Event<T>> {
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+	private final Observable observable;
+	private final Object     successKey;
+	private final Object     failureKey;
+
+	protected Action(Observable observable, Object successKey, Object failureKey) {
+		this.observable = observable;
+		this.successKey = successKey;
+		this.failureKey = failureKey;
+	}
+
+	protected Action(Observable observable, Object successKey) {
+		this(observable, successKey, null);
+	}
+
+	@Override
+	public final void accept(Event<T> tEvent) {
+		try {
+			doAccept(tEvent);
+		} catch(Throwable e) {
+			log.error(e.getMessage(), e);
+			notifyError(e);
+		}
+	}
+
+	public Observable getObservable() {
+		return observable;
+	}
+
+	public Object getSuccessKey() {
+		return successKey;
+	}
+
+	public Object getFailureKey() {
+		return failureKey;
+	}
+
+	@Override
+	public String toString() {
+		return ""+System.identityHashCode(this);
+	}
+
+	protected void notifyValue(Event<?> value) {
+		observable.notify(successKey, value);
+	}
+
+	/**
+	 * Notify this {@code Composable} that an error is being propagated through this {@code Observable}.
+	 *
+	 * @param error
+	 * 		the error to propagate
+	 */
+	protected void notifyError(Throwable error) {
+		observable.notify(failureKey != null ? failureKey : error.getClass(), Event.wrap(error));
+	}
+
+	protected abstract void doAccept(Event<T> ev);
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/ActionUtils.java b/reactor-core/src/main/java/reactor/core/action/ActionUtils.java
new file mode 100644
index 0000000..2b5f68a
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/ActionUtils.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Reactor;
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+import reactor.function.Consumer;
+
+import java.util.Arrays;
+
+/**
+ * @author Stephane Maldini
+ */
+public abstract class ActionUtils {
+
+	public static String browseReactor(Reactor reactor) {
+		ActionVisitor actionVisitor = new ActionVisitor(reactor, true);
+		actionVisitor.loopRegistredActions(reactor.getConsumerRegistry(), 1, "accept");
+		return actionVisitor.toString();
+	}
+
+	public static String browseAction(Action action) {
+		ActionVisitor actionVisitor = new ActionVisitor((Reactor) action.getObservable(), true);
+		actionVisitor.parseAction(action, 0, "accept");
+		return actionVisitor.toString();
+	}
+
+
+	public static String browseReactor(Reactor reactor, Object successKey, Object failureKey, Object flushKey) {
+		ActionVisitor actionVisitor = new ActionVisitor(reactor, true);
+		actionVisitor.drawReactorConsumers(reactor, successKey, failureKey, flushKey, 1);
+		return actionVisitor.toString();
+	}
+
+	public static class ActionVisitor {
+
+		final private boolean       visitFailures;
+		final private StringBuilder appender;
+
+		private ActionVisitor(Reactor reactor, boolean visitFailures) {
+			this.appender = new StringBuilder("\nreactor(" + reactor.getId() + ")");
+			this.visitFailures = visitFailures;
+		}
+
+		private ActionVisitor(Reactor reactor) {
+			this(reactor, false);
+		}
+
+		private ActionVisitor drawReactorConsumers(Reactor reactor, Object successKey, Object failureKey, Object flushKey,
+		                                           int d) {
+
+			if (successKey != null) {
+				loopRegistredActions(reactor.getConsumerRegistry().select(successKey), d, "accept");
+			}
+			if (flushKey != null) {
+				loopRegistredActions(reactor.getConsumerRegistry().select(flushKey), d, "flush");
+			}
+			if (visitFailures && failureKey != null)
+				loopRegistredActions(reactor.getConsumerRegistry().select(failureKey), d, "fail");
+
+			return this;
+		}
+
+		private void parseAction(Object action, int d, String marker) {
+			appender.append("\n");
+			for (int i = 0; i < d; i++)
+				appender.append("|   ");
+			appender.append("|____" + marker + ":");
+
+			appender.append(action.getClass().getSimpleName().isEmpty() ? action :
+					action
+							.getClass()
+							.getSimpleName().replaceAll("Action", "") + "[" + action + "]");
+
+			if (Action.class.isAssignableFrom(action.getClass())) {
+				Action<?> operation;
+
+				if((FlushableAction.class.isAssignableFrom(action.getClass()) &&
+						Action.class.isAssignableFrom(((FlushableAction) action).getFlushable().getClass()))){
+					operation = (Action)((FlushableAction) action).getFlushable();
+				}else{
+					operation = ((Action) action);
+				}
+
+				renderBatch(operation, d);
+				renderFilter(operation, d);
+
+				drawReactorConsumers(
+						(Reactor) operation.getObservable(),
+						operation.getSuccessKey(),
+						operation.getFailureKey(),
+						null,
+						d + 1
+				);
+			}
+		}
+
+		private void loopRegistredActions(Iterable<Registration<? extends Consumer<? extends Event<?>>>> operations, int d,
+		                                  String marker) {
+			for (Registration<?> registration : operations) {
+				parseAction(registration.getObject(), d, marker);
+			}
+		}
+
+		private void renderFilter(Object consumer, int d) {
+			if (FilterAction.class.isAssignableFrom(consumer.getClass())) {
+				FilterAction operation = (FilterAction) consumer;
+
+				if (operation.getElseObservable() != null) {
+					loopRegistredActions(((Reactor) operation.getElseObservable()).getConsumerRegistry()
+							.select(operation.getElseSuccess()),
+							d + 1, "else");
+				}
+			}
+		}
+
+
+		private void renderBatch(Object consumer, int d) {
+			if (BatchAction.class.isAssignableFrom(consumer.getClass())) {
+				BatchAction operation = (BatchAction) consumer;
+				loopRegistredActions(((Reactor) operation.getObservable()).getConsumerRegistry().select(operation.getFirstKey()),
+						d + 1, "first");
+				loopRegistredActions(((Reactor) operation.getObservable()).getConsumerRegistry().select(operation.getFlushKey()),
+						d + 1, "flush");
+			}
+		}
+
+		@Override
+		public String toString() {
+			return appender.toString();
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/BatchAction.java b/reactor-core/src/main/java/reactor/core/action/BatchAction.java
new file mode 100644
index 0000000..b45877e
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/BatchAction.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @author Stephane Maldini
+ */
+public class BatchAction<T> extends Action<T>{
+
+	protected final ReentrantLock lock = new ReentrantLock();
+
+	private final int    batchSize;
+	private final Object flushKey;
+	private final Object firstKey;
+
+	private volatile long errorCount  = 0l;
+	private volatile long acceptCount = 0l;
+
+	public BatchAction(int batchSize,
+	                   Observable d,
+	                   Object successKey,
+	                   Object failureKey) {
+		this(batchSize, d, successKey, failureKey, null, null);
+	}
+
+	public BatchAction(int batchSize,
+	                   Observable d,
+	                   Object successKey,
+	                   Object failureKey,
+	                   Object flushKey,
+	                   Object firstKey) {
+		super(d, successKey, failureKey);
+		this.batchSize = batchSize;
+		this.flushKey = flushKey;
+		this.firstKey = firstKey;
+	}
+
+	public Object getFlushKey() {
+		return flushKey;
+	}
+
+	public Object getFirstKey() {
+		return firstKey;
+	}
+
+	public long getErrorCount() {
+			return errorCount;
+	}
+
+	public long getAcceptCount() {
+			return acceptCount;
+	}
+
+	public int getBatchSize() {
+		return batchSize;
+	}
+
+	protected void doNext(Event<T> event) {
+		if (getSuccessKey() != null) {
+			notifyValue(event);
+		}
+	}
+
+	protected void doFlush(Event<T> event) {
+		if (flushKey != null) {
+			getObservable().notify(flushKey, event);
+		}
+	}
+
+	protected void doFirst(Event<T> event) {
+		if (firstKey != null) {
+			getObservable().notify(firstKey, event);
+		}
+	}
+
+	@Override
+	public void doAccept(Event<T> value) {
+		if(batchSize == -1){
+			doNext(value);
+			return;
+		}
+
+		lock.lock();
+		long accepted;
+		try {
+			accepted = (++acceptCount) % batchSize;
+
+			if (accepted == 1) {
+				doFirst(value);
+			}
+
+			doNext(value);
+
+			if (accepted == 0) {
+				doFlush(value);
+			}
+		} finally {
+			lock.unlock();
+		}
+
+	}
+
+	@Override
+	protected void notifyError(Throwable error) {
+		lock.lock();
+		try {
+			errorCount++;
+			super.notifyError(error);
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	public String toString() {
+		return super.toString()+"  % size:"+batchSize+" %  accepted:"+acceptCount+" % errors:"+errorCount;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/BufferAction.java b/reactor-core/src/main/java/reactor/core/action/BufferAction.java
new file mode 100644
index 0000000..962e79b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/BufferAction.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Consumer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class BufferAction<T> extends BatchAction<T> implements Flushable<T> {
+
+	private final List<Event<T>> values;
+
+	public static final Event<Object> BUFFER_FLUSH = Event.wrap(null);
+
+	final private Consumer<Iterable<Event<T>>> batchConsumer;
+
+	public BufferAction(int batchSize, final Observable d, Object successKey, Object failureKey, final Object flushKey) {
+		super(batchSize, d, successKey, failureKey);
+		if (flushKey == null) {
+			this.batchConsumer = d.batchNotify(successKey);
+		} else {
+			this.batchConsumer = d.batchNotify(successKey, new Consumer<Void>() {
+				@Override
+				public void accept(Void aVoid) {
+					d.notify(flushKey, BUFFER_FLUSH);
+				}
+			});
+		}
+		this.values = new ArrayList<Event<T>>(batchSize > 1 ? batchSize : 256);
+	}
+
+	@Override
+	public void doNext(Event<T> value) {
+		values.add(value);
+	}
+
+
+	@Override
+	public void doFlush(Event<T> value) {
+		if (values.isEmpty()) {
+			return;
+		}
+		batchConsumer.accept(new ArrayList<Event<T>>(values));
+		values.clear();
+	}
+
+	@Override
+	public Flushable<T> flush() {
+		lock.lock();
+		try {
+			doFlush(null);
+		} finally {
+			lock.unlock();
+		}
+		return this;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/CallbackAction.java b/reactor-core/src/main/java/reactor/core/action/CallbackAction.java
new file mode 100644
index 0000000..67fc4c7
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/CallbackAction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Consumer;
+
+/**
+ * @author Stephane Maldini
+ */
+public class CallbackAction<T> extends Action<T> {
+
+	private final Consumer<T> consumer;
+
+	public CallbackAction(Consumer<T> consumer, Observable d, Object failureKey) {
+		super(d, null, failureKey);
+		this.consumer = consumer;
+	}
+
+	@Override
+	public void doAccept(Event<T> value) {
+		consumer.accept(value.getData());
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/CallbackEventAction.java b/reactor-core/src/main/java/reactor/core/action/CallbackEventAction.java
new file mode 100644
index 0000000..00d0755
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/CallbackEventAction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Consumer;
+
+/**
+ * @author Stephane Maldini
+ */
+public class CallbackEventAction<T> extends Action<T> {
+
+	private final Consumer<Event<T>> consumer;
+
+	public CallbackEventAction(Consumer<Event<T>> consumer, Observable d, Object failureKey) {
+		super(d, null, failureKey);
+		this.consumer = consumer;
+	}
+
+	@Override
+	public void doAccept(Event<T> value) {
+		consumer.accept(value);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/CollectAction.java b/reactor-core/src/main/java/reactor/core/action/CollectAction.java
new file mode 100644
index 0000000..1697e80
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/CollectAction.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class CollectAction<T> extends BatchAction<T> implements Flushable<T> {
+
+	private final List<T> values;
+
+	public CollectAction(int batchsize, Observable d, Object successKey, Object failureKey) {
+		super(batchsize, d, successKey, failureKey);
+		values = new ArrayList<T>(batchsize > 0 ? batchsize : 256);
+	}
+
+	@Override
+	public void doNext(Event<T> value) {
+		values.add(value.getData());
+	}
+
+	@Override
+	public void doFlush(Event<T> ev) {
+		if (values.isEmpty()) {
+			return;
+		}
+		notifyValue(Event.wrap(new ArrayList<T>(values)));
+		values.clear();
+	}
+
+	@Override
+	public Flushable<T> flush() {
+		lock.lock();
+		try {
+			doFlush(null);
+		} finally {
+			lock.unlock();
+		}
+		return this;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/ConnectAction.java b/reactor-core/src/main/java/reactor/core/action/ConnectAction.java
new file mode 100644
index 0000000..a6937e4
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/ConnectAction.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+
+/**
+ * @author Stephane Maldini
+ */
+public class ConnectAction<T> extends Action<T> implements Flushable<T> {
+
+	public ConnectAction(Observable observable, Object successKey, Object failureKey) {
+		super(observable, successKey, failureKey);
+	}
+
+	@Override
+	public void doAccept(Event<T> event) {
+		notifyValue(event);
+	}
+
+	@Override
+	public Flushable<T> flush() {
+		return this;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/CountAction.java b/reactor-core/src/main/java/reactor/core/action/CountAction.java
new file mode 100644
index 0000000..43054a8
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/CountAction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class CountAction<T> extends Action<T> implements Flushable<T>{
+
+	private final AtomicLong counter = new AtomicLong(0l);
+
+	public CountAction(Observable d, Object successKey, Object failureKey) {
+		super(d, successKey, failureKey);
+	}
+
+	@Override
+	public void doAccept(Event<T> value) {
+		counter.getAndIncrement();
+	}
+
+
+	@Override
+	public CountAction<T> flush() {
+		notifyValue(Event.wrap(counter.get()));
+		counter.set(0l);
+		return this;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/DistinctAction.java b/reactor-core/src/main/java/reactor/core/action/DistinctAction.java
new file mode 100644
index 0000000..f66a700
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/DistinctAction.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+import reactor.function.Consumer;
+import reactor.timer.Timer;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class DistinctAction<T> extends Action<T> {
+
+	private T lastData;
+
+	public DistinctAction(Observable d, Object successKey, Object failureKey) {
+		super(d, successKey, failureKey);
+	}
+
+	@Override
+	protected void doAccept(Event<T> ev) {
+		final T currentData = ev.getData();
+		if(currentData == null || !currentData.equals(lastData)){
+			lastData = currentData;
+			notifyValue(ev);
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/FilterAction.java b/reactor-core/src/main/java/reactor/core/action/FilterAction.java
new file mode 100644
index 0000000..90544ee
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/FilterAction.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Predicate;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class FilterAction<T> extends Action<T> {
+
+	public static final Predicate<Boolean> simplePredicate = new Predicate<Boolean>() {
+		@Override
+		public boolean test(Boolean aBoolean) {
+			return aBoolean;
+		}
+	};
+
+	private final Predicate<T> p;
+	private final Observable   elseObservable;
+
+	private final Object       elseSuccess;
+
+	public FilterAction(Predicate<T> p, Observable d, Object successKey, Object failureKey,
+	                    Observable elseObservable, Object elseSuccess
+	) {
+		super(d, successKey, failureKey);
+		this.p = p;
+		this.elseSuccess = elseSuccess;
+		this.elseObservable = elseObservable;
+	}
+
+	@Override
+	public void doAccept(Event<T> value) {
+		boolean b = p.test(value.getData());
+		if (b) {
+			notifyValue(value);
+		} else {
+			if (null != elseObservable) {
+				elseObservable.notify(elseSuccess, value);
+			}
+			// GH-154: Verbose error level logging of every event filtered out by a Stream filter
+			// Fix: ignore Predicate failures and drop values rather than notifying of errors.
+			//d.accept(new IllegalArgumentException(String.format("%s failed a predicate test.", value)));
+		}
+	}
+
+	public Object getElseSuccess() {
+		return elseSuccess;
+	}
+
+	public Observable getElseObservable() {
+		return elseObservable;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/Flushable.java b/reactor-core/src/main/java/reactor/core/action/Flushable.java
new file mode 100644
index 0000000..bdb5b1b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/Flushable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.event.Event;
+
+/**
+ * Component that can be flushed
+ *
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public interface Flushable<T> {
+
+	static final Event<Object> FLUSH_EVENT = Event.wrap(null);
+
+	/**
+	 * Trigger flush on this component, generally draining any collected values.
+	 */
+	Flushable<T> flush();
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/FlushableAction.java b/reactor-core/src/main/java/reactor/core/action/FlushableAction.java
new file mode 100644
index 0000000..14a7b77
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/FlushableAction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class FlushableAction extends Action<Object> {
+
+	private final Flushable<?> flushable;
+
+	public FlushableAction(Flushable<?> flushable, Observable observable, Object failureKey) {
+		super(observable, null, failureKey);
+		this.flushable = flushable;
+	}
+
+	@Override
+	public void doAccept(Event<Object> value) {
+		flushable.flush();
+	}
+
+	@Override
+	public String toString() {
+		return flushable.getClass().getSimpleName().replaceAll("Action"," ")+"["+flushable.toString()+"]";
+	}
+
+	public Flushable<?> getFlushable() {
+		return flushable;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/ForEachAction.java b/reactor-core/src/main/java/reactor/core/action/ForEachAction.java
new file mode 100644
index 0000000..bd3be14
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/ForEachAction.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import com.gs.collections.impl.block.function.checked.CheckedFunction;
+import com.gs.collections.impl.utility.Iterate;
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Consumer;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class ForEachAction<T> extends Action<Iterable<T>> implements Flushable<T> {
+
+	public static final Event<Object> FOREACH_FLUSH = Event.wrap(null);
+
+	final private Consumer<Iterable<Event<T>>> batchConsumer;
+	final private Iterable<T>                  defaultValues;
+
+	public ForEachAction(Observable d,
+	                     Object successKey,
+	                     Object failureKey,
+	                     Object flushKey) {
+		this(null, d, successKey, failureKey, flushKey);
+	}
+
+
+	public ForEachAction(Iterable<T> defaultValues,
+	                     final Observable d,
+	                     Object successKey,
+	                     Object failureKey,
+	                     final Object flushKey) {
+		super(d, successKey, failureKey);
+		this.defaultValues = defaultValues;
+		this.batchConsumer = d.batchNotify(successKey, new Consumer<Void>() {
+			@Override
+			public void accept(Void aVoid) {
+				d.notify(flushKey, FOREACH_FLUSH);
+			}
+		});
+	}
+
+	@Override
+	public void doAccept(final Event<Iterable<T>> value) {
+		Iterable<T> data;
+		if (null == (data = value.getData())) {
+			return;
+		}
+		batchConsumer.accept(Iterate.collect(data, new CheckedFunction<T, Event<T>>() {
+			@Override
+			public Event<T> safeValueOf(T data) throws Exception {
+				return value.copy(data);
+			}
+		}));
+	}
+
+	@Override
+	public Flushable<T> flush() {
+		doAccept(Event.wrap(defaultValues));
+		return this;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/MapAction.java b/reactor-core/src/main/java/reactor/core/action/MapAction.java
new file mode 100644
index 0000000..e990773
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/MapAction.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Function;
+
+/**
+ * @author Stephane Maldini
+ */
+public class MapAction<T, V> extends Action<T> {
+
+	private final Function<T, V> fn;
+
+	public MapAction(Function<T, V> fn, Observable d, Object successKey, Object failureKey) {
+		super(d, successKey, failureKey);
+		this.fn = fn;
+	}
+
+	@Override
+	public void doAccept(Event<T> value) {
+		V val = fn.apply(value.getData());
+		notifyValue(value.copy(val));
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/MapManyAction.java b/reactor-core/src/main/java/reactor/core/action/MapManyAction.java
new file mode 100644
index 0000000..fb2f0ec
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/MapManyAction.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.core.composable.Composable;
+import reactor.event.Event;
+import reactor.function.Function;
+
+/**
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public class MapManyAction<T, V, C extends Composable<V>> extends Action<T> {
+
+	public static final Event<Object> MERGE_FLUSH = Event.wrap(null);
+
+	private final Object         flushKey;
+	private final Function<T, C> fn;
+
+	public MapManyAction(Function<T, C> fn,
+	                     Observable ob,
+	                     Object successKey,
+	                     Object failureKey,
+	                     Object flushKey) {
+		super(ob, successKey, failureKey);
+		this.fn = fn;
+		this.flushKey = flushKey;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public void doAccept(Event<T> value) {
+		C c = fn.apply(value.getData());
+		c.add(new ConnectAction<V>(getObservable(), getSuccessKey(), getFailureKey()) {
+			@Override
+			public Flushable<V> flush() {
+				getObservable().notify(flushKey, MERGE_FLUSH);
+				return this;
+			}
+		});
+		c.flush();
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/MovingWindowAction.java b/reactor-core/src/main/java/reactor/core/action/MovingWindowAction.java
new file mode 100644
index 0000000..9c1e232
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/MovingWindowAction.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.timer.Timer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * MovingWindowAction is collecting maximum {@param backlog} events on a stream
+ * until {@param period} is reached, after that steams collected events further,
+ * and continues collecting without clearing the list.
+ */
+public class MovingWindowAction<T> extends WindowAction<T> {
+
+  private final ReentrantLock lock    = new ReentrantLock();
+  private final AtomicInteger pointer = new AtomicInteger(0);
+  private final T[]           collectedWindow;
+
+	@SuppressWarnings("unchecked")
+  public MovingWindowAction(Observable d,
+                            Object successKey,
+                            Object failureKey,
+                            Timer timer,
+                            int period, TimeUnit timeUnit, int delay, int backlog
+  ) {
+    super(d, successKey, failureKey, timer, period, timeUnit, delay);
+    this.collectedWindow = (T[]) new Object[backlog];
+  }
+
+  @Override
+  protected void doWindow(Long aLong) {
+    lock.lock();
+    try {
+      int currentPointer = pointer.get();
+
+      List<T> window = new ArrayList<T>();
+      int adjustedPointer = adjustPointer(currentPointer);
+
+      if (currentPointer > collectedWindow.length) {
+        for(int i = adjustedPointer; i < collectedWindow.length; i++) {
+          window.add(collectedWindow[i]);
+        }
+      }
+
+      for(int i = 0; i < adjustedPointer; i++) {
+        window.add(collectedWindow[i]);
+      }
+
+      notifyValue(Event.wrap(window));
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  @Override
+  public void doAccept(Event<T> value) {
+    lock.lock();
+    try {
+      int index = adjustPointer(pointer.getAndIncrement());
+      collectedWindow[index] = value.getData();
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  private int adjustPointer(int pointer) {
+    return pointer % collectedWindow.length;
+  }
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/action/Pipeline.java b/reactor-core/src/main/java/reactor/core/action/Pipeline.java
new file mode 100644
index 0000000..8fe9458
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/Pipeline.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+/**
+ * Component that can be injected with {@link Action}s and consume flush events for releasing buffer owned by the
+ * pipeline
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public interface Pipeline<T> extends Flushable<T>{
+
+	/**
+	 * Consume events with the passed {@code Action}
+	 *
+	 * @param action
+	 * 		the action listening for values
+	 */
+	Pipeline<T> add(Action<T> action);
+
+
+	/**
+	 * Consume flush with the passed {@link Flushable}
+	 *
+	 * @param action
+	 * 		the action listening for flush
+	 *
+	 * @return {@literal this}
+	 */
+	Pipeline<T> consumeFlush(Flushable<?> action);
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/ReduceAction.java b/reactor-core/src/main/java/reactor/core/action/ReduceAction.java
new file mode 100644
index 0000000..8367512
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/ReduceAction.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Function;
+import reactor.function.Supplier;
+import reactor.tuple.Tuple;
+import reactor.tuple.Tuple2;
+
+/**
+ * @author Stephane Maldini
+ */
+public class ReduceAction<T, A> extends BatchAction<T> implements Flushable<T> {
+	private final    Supplier<A>               accumulators;
+	private final    Function<Tuple2<T, A>, A> fn;
+	private volatile A                         acc;
+
+	public ReduceAction(int batchSize, Supplier<A> accumulators, Function<Tuple2<T, A>, A> fn,
+	                    Observable d, Object successKey, Object failureKey) {
+		super(batchSize, d, successKey, failureKey);
+		this.accumulators = accumulators;
+		this.fn = fn;
+	}
+
+	@Override
+	protected void doNext(Event<T> ev) {
+		if (null == acc) {
+			acc = (null != accumulators ? accumulators.get() : null);
+		}
+		acc = fn.apply(Tuple2.of(ev.getData(), acc));
+	}
+
+	@Override
+	protected void doFlush(Event<T> ev) {
+		if (acc != null) {
+			notifyValue(ev.copy(acc));
+			acc = null;
+		}
+	}
+
+	@Override
+	public Flushable<T> flush() {
+		lock.lock();
+		try {
+			if (acc != null) {
+				notifyValue(Event.wrap(acc));
+			}
+		} finally {
+			lock.unlock();
+		}
+		return this;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/ScanAction.java b/reactor-core/src/main/java/reactor/core/action/ScanAction.java
new file mode 100644
index 0000000..51ec85c
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/ScanAction.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Function;
+import reactor.function.Supplier;
+import reactor.tuple.Tuple;
+import reactor.tuple.Tuple2;
+
+/**
+ * @author Stephane Maldini
+ */
+public class ScanAction<T, A> extends Action<T> {
+
+	private final    Supplier<A>               accumulators;
+	private final    Function<Tuple2<T, A>, A> fn;
+	private volatile A                         acc;
+
+
+	public ScanAction(Supplier<A> accumulators, Function<Tuple2<T, A>, A> fn,
+	                  Observable d, Object successKey, Object failureKey) {
+		super(d, successKey, failureKey);
+		this.accumulators = accumulators;
+		this.fn = fn;
+	}
+
+	@Override
+	protected void doAccept(Event<T> ev) {
+		if (null == acc) {
+			acc = (null != accumulators ? accumulators.get() : null);
+		}
+		acc = fn.apply(Tuple.of(ev.getData(), acc));
+		notifyValue(ev.copy(acc));
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/SupplyAction.java b/reactor-core/src/main/java/reactor/core/action/SupplyAction.java
new file mode 100644
index 0000000..4538de4
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/SupplyAction.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Supplier;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class SupplyAction<T> extends Action<Object> implements Flushable<T> {
+
+	private final Supplier<T> supplier;
+
+	public SupplyAction(Supplier<T> supplier, Observable d, Object successKey, Object failureKey) {
+		super(d, successKey, failureKey);
+		this.supplier = supplier;
+	}
+
+	@Override
+	public void doAccept(Event<Object> value) {
+		notifyValue(Event.wrap(supplier.get()));
+	}
+
+	@Override
+	public Flushable<T> flush() {
+		doAccept(null);
+		return this;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/TimeoutAction.java b/reactor-core/src/main/java/reactor/core/action/TimeoutAction.java
new file mode 100644
index 0000000..c7157b7
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/TimeoutAction.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+import reactor.function.Consumer;
+import reactor.timer.Timer;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class TimeoutAction<T> extends Action<T> {
+
+	public static final Event<Object> TIMEOUT_EVENT = Event.wrap(null);
+	private final Timer timer;
+	private final long  timeout;
+	private final Consumer<Long> timeoutTask = new Consumer<Long>() {
+		@Override
+		public void accept(Long aLong) {
+			if (timeoutRegistration == null || timeoutRegistration.getObject() == this) {
+				notifyValue(TIMEOUT_EVENT);
+			}
+		}
+	};
+
+	private Registration<? extends Consumer<Long>> timeoutRegistration;
+
+	public TimeoutAction(Observable d, Object successKey, Object failureKey,
+	                     Timer timer, long timeout) {
+		super(d, successKey, failureKey);
+		this.timer = timer;
+		this.timeout = timeout;
+		timeoutRegistration = timer.submit(timeoutTask, timeout, TimeUnit.MILLISECONDS);
+	}
+
+	@Override
+	protected void doAccept(Event<T> ev) {
+		timeoutRegistration = timer.submit(timeoutTask, timeout, TimeUnit.MILLISECONDS);
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/WhenAction.java b/reactor-core/src/main/java/reactor/core/action/WhenAction.java
new file mode 100644
index 0000000..4573947
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/WhenAction.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.function.Consumer;
+import reactor.function.Predicate;
+
+/**
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+public class WhenAction<T> extends Action<T> {
+
+	private final Predicate<T> consumer;
+
+	public WhenAction(Predicate<T> consumer, Observable d, Object successKey, Object failureKey) {
+		super(d, successKey, failureKey);
+		this.consumer = consumer;
+	}
+
+	@Override
+	public void doAccept(Event<T> value) {
+		if(consumer.test(value.getData())){
+			getObservable().notify(getSuccessKey(), Flushable.FLUSH_EVENT);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/action/WindowAction.java b/reactor-core/src/main/java/reactor/core/action/WindowAction.java
new file mode 100644
index 0000000..54fefd5
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/action/WindowAction.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.action;
+
+import reactor.core.Observable;
+import reactor.event.Event;
+import reactor.event.lifecycle.Pausable;
+import reactor.event.registry.Registration;
+import reactor.function.Consumer;
+import reactor.timer.Timer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * WindowAction is collecting events on a steam until {@param period} is reached,
+ * after that streams collected events further, clears the internal collection and
+ * starts collecting items from empty list.
+ *
+ * @author Stephane Maldini
+ */
+public class WindowAction<T> extends Action<T> implements Pausable, Flushable<T> {
+
+	private final ReentrantLock lock            = new ReentrantLock();
+	private final List<T>       collectedWindow = new ArrayList<T>();
+	private final Registration<? extends Consumer<Long>> timerRegistration;
+
+
+	public WindowAction(Observable d,
+	                    Object successKey,
+	                    Object failureKey,
+	                    Timer timer,
+	                    int period, TimeUnit timeUnit, int delay
+  ) {
+		super(d, successKey, failureKey);
+		this.timerRegistration = timer.schedule(new Consumer<Long>() {
+			@Override
+			public void accept(Long aLong) {
+				doWindow(aLong);
+			}
+		}, period, timeUnit, delay);
+	}
+
+	protected void doWindow(Long aLong) {
+		lock.lock();
+		try {
+			if(!collectedWindow.isEmpty()){
+				notifyValue(Event.wrap(new ArrayList<T>(collectedWindow)));
+				collectedWindow.clear();
+			}
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	public void doAccept(Event<T> value) {
+		lock.lock();
+		try {
+			collectedWindow.add(value.getData());
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	public Pausable cancel() {
+		timerRegistration.cancel();
+		return this;
+	}
+
+	@Override
+	public Pausable pause() {
+		timerRegistration.pause();
+		return this;
+	}
+
+	@Override
+	public Pausable resume() {
+		timerRegistration.resume();
+		return this;
+	}
+
+	@Override
+	public Flushable<T> flush() {
+		doWindow(-1l);
+		return this;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/Composable.java b/reactor-core/src/main/java/reactor/core/composable/Composable.java
new file mode 100644
index 0000000..8e86c86
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/Composable.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.composable;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.Reactor;
+import reactor.core.action.*;
+import reactor.event.Event;
+import reactor.event.selector.ObjectSelector;
+import reactor.event.selector.Selector;
+import reactor.event.selector.Selectors;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.function.Predicate;
+import reactor.function.Supplier;
+import reactor.timer.Timer;
+import reactor.tuple.Tuple2;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Abstract base class for components designed to provide a succinct API for working with future values. Provides base
+ * functionality and an internal contract for subclasses that make use of the {@link #map(reactor.function.Function)}
+ * and {@link #filter(reactor.function.Predicate)} methods.
+ *
+ * @param <T>
+ * 		The type of the values
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ */
+public abstract class Composable<T> implements Pipeline<T> {
+
+	public static final Event<Object> END_EVENT = Event.wrap(null);
+
+	private final Selector acceptSelector;
+	private final Object   acceptKey;
+	private final Selector error = Selectors.anonymous();
+	private final Selector flush = Selectors.anonymous();
+	private final Environment environment;
+
+	private final Observable    events;
+	private final Composable<?> parent;
+
+	protected <U> Composable(@Nullable Observable observable, @Nullable Composable<U> parent) {
+		this(observable, parent, null, null);
+	}
+
+
+	protected <U> Composable(@Nullable Observable observable, @Nullable Composable<U> parent,
+	                         @Nullable Tuple2<Selector, Object> acceptSelectorTuple,
+	                         @Nullable Environment environment) {
+		Assert.state(observable != null || parent != null, "One of 'observable' or 'parent'  cannot be null.");
+		this.parent = parent;
+		this.environment = environment;
+		this.events = parent == null ? observable : parent.events;
+		if (null == acceptSelectorTuple) {
+			this.acceptSelector = Selectors.anonymous();
+			this.acceptKey = acceptSelector.getObject();
+		} else {
+			this.acceptKey = acceptSelectorTuple.getT1();
+			this.acceptSelector = new ObjectSelector<Object>(acceptSelectorTuple.getT2());
+		}
+	}
+
+
+	/**
+	 * Assign an error handler to exceptions of the given type.
+	 *
+	 * @param exceptionType
+	 * 		the type of exceptions to handle
+	 * @param onError
+	 * 		the error handler for each exception
+	 * @param <E>
+	 * 		type of the exception to handle
+	 *
+	 * @return {@literal this}
+	 */
+	public <E extends Throwable> Composable<T> when(@Nonnull final Class<E> exceptionType,
+	                                                @Nonnull final Consumer<E> onError) {
+		this.events.on(error, new Action<E>(events, null) {
+			@Override
+			protected void doAccept(Event<E> e) {
+				if (Selectors.T(exceptionType).matches(e.getData().getClass())) {
+					onError.accept(e.getData());
+				}
+			}
+
+			public String toString() {
+				return "When[" + exceptionType.getSimpleName() + "]";
+			}
+
+		});
+		return this;
+	}
+
+	/**
+	 * Attach another {@code Composable} to this one that will cascade the value or error received by this {@code
+	 * Composable} into the next.
+	 *
+	 * @param composable
+	 * 		the next {@code Composable} to cascade events to
+	 *
+	 * @return {@literal this}
+	 * @since 1.1
+	 */
+	public Composable<T> connect(@Nonnull final Composable<T> composable) {
+		this.connectValues(composable);
+		this.consumeErrorAndFlush(composable);
+		return this;
+	}
+
+	/**
+	 * Attach another {@code Composable} to this one that will only cascade the value received by this {@code
+	 * Composable} into the next.
+	 *
+	 * @param composable
+	 * 		the next {@code Composable} to cascade events to
+	 *
+	 * @return {@literal this}
+	 */
+	public Composable<T> connectValues(@Nonnull final Composable<T> composable) {
+		if(composable == this) {
+			throw new IllegalArgumentException("Trying to consume itself, leading to erroneous recursive calls");
+		}
+		add(new ConnectAction<T>(composable.events, composable.acceptKey, composable.error.getObject()));
+
+		return this;
+	}
+
+	/**
+	 * Attach a {@link Consumer} to this {@code Composable} that will consume any values accepted by this {@code
+	 * Composable}.
+	 *
+	 * @param consumer
+	 * 		the conumer to invoke on each value
+	 *
+	 * @return {@literal this}
+	 */
+	public Composable<T> consume(@Nonnull final Consumer<T> consumer) {
+		add(new CallbackAction<T>(consumer, events, error.getObject()));
+		return this;
+	}
+
+	/**
+	 * Attach a {@link Consumer<Event>} to this {@code Composable} that will consume any values accepted by this {@code
+	 * Composable}.
+	 *
+	 * @param consumer
+	 * 		the conumer to invoke on each value
+	 *
+	 * @return {@literal this}
+	 */
+	public Composable<T> consumeEvent(@Nonnull final Consumer<Event<T>> consumer) {
+		add(new CallbackEventAction<T>(consumer, events, error.getObject()));
+		return this;
+	}
+
+	/**
+	 * Pass values accepted by this {@code Composable} into the given {@link Observable}, notifying with the given key.
+	 *
+	 * @param key
+	 * 		the key to notify on
+	 * @param observable
+	 * 		the {@link Observable} to notify
+	 *
+	 * @return {@literal this}
+	 */
+	public Composable<T> consume(@Nonnull final Object key, @Nonnull final Observable observable) {
+		add(new ConnectAction<T>(observable, key, null));
+		return this;
+	}
+
+	/**
+	 * Assign the given {@link Function} to transform the incoming value {@code T} into a {@code V} and pass it into
+	 * another {@code Composable}.
+	 *
+	 * @param fn
+	 * 		the transformation function
+	 * @param <V>
+	 * 		the type of the return value of the transformation function
+	 *
+	 * @return a new {@code Composable} containing the transformed values
+	 */
+	public <V> Composable<V> map(@Nonnull final Function<T, V> fn) {
+		Assert.notNull(fn, "Map function cannot be null.");
+		final Composable<V> d = newComposable();
+		add(new MapAction<T, V>(
+				fn,
+				d.getObservable(),
+				d.getAcceptKey(),
+				d.getError().getObject())).consumeErrorAndFlush(d);
+
+		return d;
+	}
+
+	/**
+	 * Assign the given {@link Function} to transform the incoming value {@code T} into a {@code Composable<V>} and pass
+	 * it into another {@code Composable}.
+	 *
+	 * @param fn
+	 * 		the transformation function
+	 * @param <V>
+	 * 		the type of the return value of the transformation function
+	 *
+	 * @return a new {@code Composable} containing the transformed values
+	 * @since 1.1
+	 */
+	public <V, C extends Composable<V>> Composable<V> mapMany(@Nonnull final Function<T, C> fn) {
+		Assert.notNull(fn, "FlatMap function cannot be null.");
+		final Composable<V> d = newComposable();
+		add(new MapManyAction<T, V, C>(
+				fn,
+				d.getObservable(),
+				d.getAcceptKey(),
+				d.getError().getObject(),
+				d.getFlush().getObject()
+				))
+				.connectErrors(d);
+		return d;
+	}
+
+	/**
+	 * {@link this#connect(Composable)} all the passed {@param composables} to this {@link Composable},
+	 * merging values streams into the current pipeline.
+	 *
+	 * @param composables
+	 * 		the the composables to connect
+	 *
+	 * @return this composable
+	 * @since 1.1
+	 */
+	public Composable<T> merge(Composable<T>... composables) {
+		for(Composable<T> composable : composables){
+			composable.connect(this);
+		}
+		return this;
+	}
+
+	/**
+	 * Evaluate each accepted value against the given predicate {@link Function}. If the predicate test succeeds, the
+	 * value is passed into the new {@code Composable}. If the predicate test fails, an exception is propagated into the
+	 * new {@code Composable}.
+	 *
+	 * @param fn
+	 * 		the predicate {@link Function} to test values against
+	 *
+	 * @return a new {@code Composable} containing only values that pass the predicate test
+	 */
+	public Composable<T> filter(@Nonnull final Function<T, Boolean> fn) {
+		return filter(new Predicate<T>() {
+			@Override
+			public boolean test(T t) {
+				return fn.apply(t);
+			}
+		}, null);
+	}
+
+	/**
+	 * Evaluate each accepted boolean value. If the predicate test succeeds, the value is
+	 * passed into the new {@code Composable}. If the predicate test fails, the value is ignored.
+	 *
+	 *
+	 * @return a new {@code Composable} containing only values that pass the predicate test
+	 * @since 1.1
+	 */
+	@SuppressWarnings("unchecked")
+	public Composable<Boolean> filter() {
+		return ((Composable<Boolean>)this).filter(FilterAction.simplePredicate, null);
+	}
+
+	/**
+	 * Evaluate each accepted boolean value. If the predicate test succeeds,
+	 * the value is passed into the new {@code Composable}. the value is propagated into the {@param
+	 * elseComposable}.
+	 *
+	 * @param elseComposable
+	 * 		the {@link Composable} to test values against
+	 *
+	 * @return a new {@code Composable} containing only values that pass the predicate test
+	 * @since 1.1
+	 */
+	@SuppressWarnings("unchecked")
+	public Composable<Boolean> filter(@Nonnull final Composable<Boolean> elseComposable) {
+		return ((Composable<Boolean>)this).filter(FilterAction.simplePredicate, elseComposable);
+	}
+
+	/**
+	 * Evaluate each accepted value against the given {@link Predicate}. If the predicate test succeeds, the value is
+	 * passed into the new {@code Composable}. If the predicate test fails, the value is ignored.
+	 *
+	 * @param p
+	 * 		the {@link Predicate} to test values against
+	 *
+	 * @return a new {@code Composable} containing only values that pass the predicate test
+	 */
+	public Composable<T> filter(@Nonnull final Predicate<T> p) {
+		return filter(p, null);
+	}
+
+	/**
+	 * Evaluate each accepted value against the given {@link Predicate}. If the predicate test succeeds, the value is
+	 * passed into the new {@code Composable}. If the predicate test fails, the value is propagated into the {@code
+	 * elseComposable}.
+	 *
+	 * @param p
+	 * 		the {@link Predicate} to test values against
+	 * @param elseComposable
+	 * 		the optional {@link reactor.core.composable.Composable} to pass rejected values
+	 *
+	 * @return a new {@code Composable} containing only values that pass the predicate test
+	 */
+	public Composable<T> filter(@Nonnull final Predicate<T> p, final Composable<T> elseComposable) {
+		final Composable<T> d = newComposable();
+		add(new FilterAction<T>(p, d.getObservable(), d.getAcceptKey(), d.getError().getObject(),
+				elseComposable != null ? elseComposable.events : null,
+				elseComposable != null ? elseComposable.acceptKey : null)).consumeErrorAndFlush(d);
+
+		if(elseComposable != null){
+			consumeErrorAndFlush(elseComposable);
+		}
+
+		return d;
+	}
+
+
+
+	/**
+	 * Flush the parent if any or the current composable otherwise when the last notification occurred before {@param
+	 * timeout} milliseconds. Timeout is run on the environment root timer.
+	 *
+	 * @param timeout
+	 * 		the timeout in milliseconds between two notifications on this composable
+	 *
+	 * @return this {@link Composable}
+	 * @since 1.1
+	 */
+	public Composable<T> timeout(long timeout) {
+		Assert.state(environment != null, "Cannot use default timer as no environment has been provided to this Stream");
+		return timeout(timeout, environment.getRootTimer());
+	}
+
+	/**
+	 * Flush the parent if any or the current composable otherwise when the last notification occurred before {@param
+	 * timeout} milliseconds. Timeout is run on the provided {@param timer}.
+	 *
+	 * @param timeout
+	 * 		the timeout in milliseconds between two notifications on this composable
+	 * @param timer
+	 * 		the reactor timer to run the timeout on
+	 *
+	 * @return this {@link Composable}
+	 * @since 1.1
+	 */
+	public Composable<T> timeout(long timeout, Timer timer) {
+		Assert.state(timer != null, "Timer must be supplied");
+		Composable<?> composable = parent != null ? parent : this;
+
+		add(new TimeoutAction<T>(
+				composable.events,
+				composable.flush.getObject(),
+				error.getObject(),
+				timer,
+				timeout
+		));
+
+		return this;
+	}
+
+	/**
+	 * Create a new {@code Composable} whose values will be generated from {@param supplier}.
+	 * Every time flush is triggered, {@param supplier} is called.
+	 *
+	 * @param supplier the supplier to drain
+	 * @return a new {@code Composable} whose values are generated on each flush
+	 * @since 1.1
+	 */
+	public Composable<T> propagate(Supplier<T> supplier) {
+
+		Composable<T> d = newComposable();
+		consumeFlush(new SupplyAction<T>(supplier,
+				d.getObservable(),
+				d.getAcceptKey(),
+				d.getError().getObject())).connectErrors(d);
+		return d;
+	}
+
+	/**
+	 * Flush any cached or unprocessed values through this {@literal Stream}.
+	 *
+	 * @return {@literal this}
+	 */
+	public Composable<T> flush() {
+		Composable<?> that = this;
+		while(that.parent != null) {
+			that = that.parent;
+		}
+
+		that.notifyFlush();
+		return this;
+	}
+
+	/**
+	 * Print a debugged form of the root composable relative to this. The output will be an acyclic directed graph of
+	 * composed actions.
+	 * @since 1.1
+	 */
+	public String debug() {
+		Composable<?> that = this;
+		while(that.parent != null) {
+			that = that.parent;
+		}
+		return ActionUtils.browseReactor((Reactor)that.events,
+		                                 that.acceptKey, that.error.getObject(), that.flush.getObject()
+		);
+	}
+
+	/**
+	 * Consume events with the passed {@code Action}
+	 *
+	 * @param action
+	 * 		the action listening for values
+	 *
+	 * @return {@literal this}
+	 * @since 1.1
+	 */
+	@SuppressWarnings("unchecked")
+	public Composable<T> add(Action<T> action) {
+		this.events.on(acceptSelector, action);
+		if(null != action && Flushable.class.isAssignableFrom(action.getClass())) {
+			consumeFlush((Flushable<T>)action);
+		}
+		return this;
+	}
+
+	@Override
+	public Composable<T> consumeFlush(Flushable<?> action) {
+		this.events.on(flush, new FlushableAction(action, events, error.getObject()));
+		return this;
+	}
+
+	/**
+	 * Forward any error to the {@param composable} argument.
+	 *
+	 * @param composable the target sink for errores and flushes
+	 *
+	 * @return this
+	 * @since 1.1
+	 */
+	public Composable<T> connectErrors(Composable<?> composable){
+		events.on(error, new ConnectAction<Throwable>(composable.events, composable.error.getObject(), null));
+		return this;
+	}
+
+	/**
+	 * Forward any error or flush to the {@param composable} argument.
+	 *
+	 * @param composable the target sink for errores and flushes
+	 *
+	 * @return this
+	 * @since 1.1
+	 */
+	protected Composable<T> consumeErrorAndFlush(Composable<?> composable){
+		events.on(flush, new ConnectAction<Void>(composable.events, composable.flush.getObject(), null));
+		return connectErrors(composable);
+	}
+
+	/**
+	 * Notify this {@code Composable} hat a flush is being requested by this {@code Composable}.
+	 */
+	void notifyFlush() {
+		events.notify(flush.getObject(), new Event<Void>(Void.class));
+	}
+
+	void notifyValue(Event<T> value) {
+		events.notify(acceptKey, value);
+	}
+
+	/**
+	 * Notify this {@code Composable} that an error is being propagated through this {@code Composable}.
+	 *
+	 * @param error
+	 * 		the error to propagate
+	 */
+	void notifyError(Throwable error) {
+		events.notify(this.error.getObject(), Event.wrap(error));
+	}
+
+	/**
+	 * Create a {@link Composable} that is compatible with the subclass of {@code Composable} in use.
+	 *
+	 * @param <V>
+	 * 		type the {@code Composable} handles
+	 *
+	 * @return a new {@code Composable} compatible with the current subclass.
+	 */
+	protected abstract <V> Composable<V> newComposable();
+
+	/**
+	 * Get the current {@link Observable}.
+	 *
+	 * @return
+	 */
+	protected Observable getObservable() {
+		return events;
+	}
+
+	/**
+	 * Get the anonymous {@link Selector} and notification key for doing accepts.
+	 *
+	 * @return
+	 */
+	protected Object getAcceptKey() {
+		return this.acceptKey;
+	}
+
+	/**
+	 * Get the anonymous {@link Selector} and notification key for doing accepts.
+	 *
+	 * @return
+	 */
+	protected Selector getAcceptSelector() {
+		return this.acceptSelector;
+	}
+
+	/**
+	 * Get the anonymous flush {@link Selector} for batch consuming.
+	 *
+	 * @return
+	 */
+	protected Selector getFlush() {
+		return this.flush;
+	}
+
+	/**
+	 * Get the anonymous {@link Selector} and notification key for doing errors.
+	 *
+	 * @return
+	 */
+	protected Selector getError() {
+		return this.error;
+	}
+
+	/**
+	 * Get the parent {@link Composable} for callback callback.
+	 *
+	 * @return
+	 */
+	protected Composable<?> getParent() {
+		return this.parent;
+	}
+
+	/**
+	 * Get the assigned {@link reactor.core.Environment}.
+	 *
+	 * @return
+	 */
+	protected Environment getEnvironment() { return environment; }
+
+
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/Deferred.java b/reactor-core/src/main/java/reactor/core/composable/Deferred.java
new file mode 100644
index 0000000..a6b2f2b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/Deferred.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.composable;
+
+import reactor.core.action.BufferAction;
+import reactor.event.Event;
+import reactor.event.support.CallbackEvent;
+import reactor.function.Consumer;
+import reactor.util.Assert;
+
+/**
+ * A Deferred is used to provide a separate between supplying values and consuming values.
+ * Values and errors are supplied by calling {@link #accept(Object)} and {@link
+ * #accept(Throwable)} respectively. Values can be consumed using the read-only
+ * {@link Composable} subclass made available by {@link #compose()}.
+ * </p>
+ * Typical usage is to create a Deferred and store it internally, only providing the
+ * enclosed {@link Composable} to clients. This ensures that clients can only consume values
+ * and cannot break the contract by also supplying them.
+ *
+ * @param <T> The type of the values
+ * @param <C> The composable subclass through which the values can be consumed
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class Deferred<T, C extends Composable<T>> implements Consumer<T> {
+
+	private final C head;
+	private final C tail;
+
+	/**
+	 * Creates a new Deferred using the given {@link Composable}
+	 *
+	 * @param composable The composable that will provide access to values
+	 */
+	public Deferred(C composable) {
+		this(composable, composable);
+	}
+	/**
+	 * Creates a new Deferred using decoupled given head and tail {@link Composable}
+	 *
+	 * @param head The composable that will provide access to values
+	 * @param tail The composable that will be used for consumption
+	 */
+	public Deferred(C head, C tail) {
+		this.head = head;
+		this.tail = tail;
+	}
+
+	/**
+	 * Accepts the given {@code error} such that it can be consumed by the
+	 * underlying {@code Composable}.
+	 *
+	 * @param error The error to accept
+	 */
+	public void accept(Throwable error) {
+		head.notifyError(error);
+	}
+
+	/**
+	 * Accepts the given {@code value} such that it can be consumed by the underlying
+	 * {@code Composable}.
+	 *
+	 * @param value The value to accept
+	 */
+	@Override
+	public void accept(T value) {
+		acceptEvent(Event.wrap(value));
+	}
+
+
+	/**
+	 * Return a {@link reactor.function.Consumer} that accepts a sequence of events  before
+	 * notifying the Composable whom must be a {@link Stream}
+	 *
+	 * @return a batch consumer ready to accept sequences
+	 */
+	public BufferAction<T> batcher() {
+		return batcher(-1);
+	}
+
+	/**
+	 * Return a {@link reactor.function.Consumer} that accepts a sequence of events  before
+	 * notifying the Composable whom must be a {@link Stream}
+	 *
+	 * @param batchSize the explicit batch size to use
+	 *
+	 * @return a batch consumer ready to accept sequences
+	 */
+	@SuppressWarnings("unchecked")
+	public BufferAction<T> batcher(int batchSize) {
+		Assert.isTrue(Stream.class.isAssignableFrom(head.getClass()), "The deferred Composable must be of type Stream");
+		return ((Stream<T>)head).bufferConsumer(batchSize);
+	}
+
+	/**
+	 * Accepts the given {@code value} such that it can be consumed by the underlying
+	 * {@code Composable}.
+	 *
+	 * @param value The value to accept
+	 */
+	public void acceptEvent(Event<T> value) {
+		head.notifyValue(value);
+	}
+
+	/**
+	 * Flush the {@code Composable} such that it can trigger batch operations.
+	 *
+	 */
+	public void flush() {
+		head.notifyFlush();
+	}
+
+	/**
+	 * Returns the underlying {@link Composable} subclass from which values and errors can be
+	 * consumed.
+	 *
+	 * @return The underlying composable
+	 */
+	public C compose() {
+		return tail;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/Promise.java b/reactor-core/src/main/java/reactor/core/composable/Promise.java
new file mode 100644
index 0000000..98a34e8
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/Promise.java
@@ -0,0 +1,620 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.composable;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.action.*;
+import reactor.core.spec.Reactors;
+import reactor.event.Event;
+import reactor.event.dispatch.Dispatcher;
+import reactor.event.selector.Selector;
+import reactor.event.selector.Selectors;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.function.Predicate;
+import reactor.function.Supplier;
+import reactor.timer.Timer;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A {@code Promise} is a stateful event processor that accepts a single value or error. In addition to {@link #get()
+ * getting} or {@link #await() awaiting} the value, consumers can be registered to be notified of {@link
+ * #onError(Consumer) notified an error}, {@link #onSuccess(Consumer) a value}, or {@link #onComplete(Consumer) both}.
+ * A
+ * promise also provides methods for composing actions with the future value much like a {@link Stream}. However, where
+ * a {@link Stream} can process many values, a {@code Promise} processes only one value or error.
+ * <p/>
+ * Reactor's {@code Promise} implementation is modeled largely after the <a href="https://github.com/promises-aplus/promises-spec">Promises/A+
+ * specification</a>, which defines a number of methods and potential actions for promises.
+ *
+ * @param <T> the type of the value that will be made available
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ * @see <a href="https://github.com/promises-aplus/promises-spec">Promises/A+ specification</a>
+ */
+public class Promise<T> extends Composable<T> implements Supplier<T> {
+
+	private final ReentrantLock lock     = new ReentrantLock();
+
+	private final long        defaultTimeout;
+	private final Condition   pendingCondition;
+
+	private State state = State.PENDING;
+	private T           value;
+	private Throwable   error;
+	private boolean hasBlockers = false;
+
+	/**
+	 * Creates a new unfulfilled promise.
+	 * <p/>
+	 * The {@code observable} is used when notifying the Promise's consumers, determining the thread on which they are
+	 * called. The given {@code env} is used to determine the default await timeout. If {@code env} is {@code null} the
+	 * default await timeout will be 30 seconds. This Promise will consumer errors from its {@code parent} such that if
+	 * the parent completes in error then so too will this Promise.
+	 *
+	 * @param observable The Observable to use to call Consumers
+	 * @param env        The Environment, if any, from which the default await timeout is obtained
+	 * @param parent     The parent, if any, from which errors are consumed
+	 */
+	public Promise(@Nullable Observable observable,
+	               @Nullable Environment env,
+	               @Nullable Composable<?> parent) {
+		super(observable, parent, null, env);
+		this.defaultTimeout = env != null ? env.getProperty("reactor.await.defaultTimeout", Long.class, 30000L) : 30000L;
+		this.pendingCondition = lock.newCondition();
+
+		consumeEvent(new Consumer<Event<T>>() {
+			@Override
+			public void accept(Event<T> event) {
+				valueAccepted(event.getData());
+			}
+		});
+		when(Throwable.class, new Consumer<Throwable>() {
+			@Override
+			public void accept(Throwable throwable) {
+				errorAccepted(throwable);
+			}
+		});
+	}
+
+	/**
+	 * Creates a new promise that has been fulfilled with the given {@code value}.
+	 * <p/>
+	 * The {@code observable} is used when notifying the Promise's consumers. The given {@code env} is used to determine
+	 * the default await timeout. If {@code env} is {@code null} the default await timeout will be 30 seconds.
+	 *
+	 * @param value      The value that fulfills the promise
+	 * @param observable The Observable to use to call Consumers
+	 * @param env        The Environment, if any, from which the default await timeout is obtained
+	 */
+	public Promise(T value,
+	               @Nullable Observable observable,
+	               @Nullable Environment env) {
+		this(observable, env, null);
+		this.value = value;
+		this.state = State.SUCCESS;
+	}
+
+	/**
+	 * Creates a new promise that has failed with the given {@code error}.
+	 * <p/>
+	 * The {@code observable} is used when notifying the Promise's consumers, determining the thread on which they are
+	 * called. The given {@code env} is used to determine the default await timeout. If {@code env} is {@code null} the
+	 * default await timeout will be 30 seconds.
+	 *
+	 * @param error      The error the completed the promise
+	 * @param env        The Environment, if any, from which the default await timeout is obtained
+	 * @param observable The Observable to use to call Consumers
+	 */
+	public Promise(Throwable error,
+	               @Nonnull Observable observable,
+	               @Nullable Environment env) {
+		this(observable, env, null);
+		this.error = error;
+		this.state = State.FAILURE;
+	}
+
+	/**
+	 * Assign a {@link Consumer} that will either be invoked later, when the {@code Promise} is completed by either
+	 * setting a value or propagating an error, or, if this {@code Promise} has already been fulfilled, is immediately
+	 * scheduled to be executed on the current {@link reactor.event.dispatch.Dispatcher}.
+	 *
+	 * @param onComplete the completion {@link Consumer}
+	 * @return {@literal this}
+	 */
+	public Promise<T> onComplete(@Nonnull final Consumer<Promise<T>> onComplete) {
+		if (isComplete()) {
+			Reactors.schedule(onComplete, this, getObservable());
+		} else {
+			getObservable().on(getFlush(), new CallbackAction<Promise<T>>(onComplete, getObservable(), null));
+		}
+		return this;
+	}
+
+	/**
+	 * Assign a {@link Consumer} that will either be invoked later, when the {@code Promise} is successfully completed
+	 * with
+	 * a value, or, if this {@code Promise} has already been fulfilled, is immediately scheduled to be executed on the
+	 * current {@link Dispatcher}.
+	 *
+	 * @param onSuccess the success {@link Consumer}
+	 * @return {@literal this}
+	 */
+	public Promise<T> onSuccess(@Nonnull final Consumer<T> onSuccess) {
+		return consume(onSuccess);
+	}
+
+	/**
+	 * Assign a {@link Consumer} that will either be invoked later, when the {@code Promise} is completed with an error,
+	 * or, if this {@code Promise} has already been fulfilled, is immediately scheduled to be executed on the current
+	 * {@link Dispatcher}.
+	 *
+	 * @param onError the error {@link Consumer}
+	 * @return {@literal this}
+	 */
+	public Promise<T> onError(@Nullable final Consumer<Throwable> onError) {
+		if (null != onError) {
+			return when(Throwable.class, onError);
+		} else {
+			return this;
+		}
+	}
+
+	/**
+	 * Assign both a success {@link Consumer} and an optional (possibly {@code null}) error {@link Consumer}.
+	 *
+	 * @param onSuccess the success {@link Consumer}
+	 * @param onError   the error {@link Consumer}
+	 * @return {@literal this}
+	 * @see #onSuccess(Consumer)
+	 * @see #onError(Consumer)
+	 */
+	public Promise<T> then(@Nonnull Consumer<T> onSuccess, @Nullable Consumer<Throwable> onError) {
+		return onSuccess(onSuccess).onError(onError);
+	}
+
+	/**
+	 * Assign a success {@link Function} that will either be invoked later, when the {@code Promise} is successfully
+	 * completed with a value, or, if this {@code Promise} has already been fulfilled, the function is immediately
+	 * scheduled to be executed on the current {@link reactor.event.dispatch.Dispatcher}.
+	 * <p/>
+	 * A new {@code Promise} is returned that will be populated by result of the given transformation {@link Function}
+	 * that
+	 * turns the incoming {@code T} into a {@code V}.
+	 *
+	 * @param onSuccess the success transformation {@link Function}
+	 * @param onError   the error {@link Consumer}
+	 * @param <V>       the type of the value returned by the transformation {@link Function}
+	 * @return a new {@code Promise} that will be populated by the result of the transformation {@link Function}
+	 */
+	public <V> Promise<V> then(@Nonnull final Function<T, V> onSuccess, @Nullable final Consumer<Throwable> onError) {
+		onError(onError);
+		return map(onSuccess);
+	}
+
+	/**
+	 * Indicates whether this {@code Promise} has been completed with either an error or a value
+	 *
+	 * @return {@code true} if this {@code Promise} is complete, {@code false} otherwise.
+	 * @see #isPending()
+	 */
+	public boolean isComplete() {
+		lock.lock();
+		try {
+			return state != State.PENDING;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	/**
+	 * Indicates whether this {@code Promise} has yet to be completed with a value or an error.
+	 *
+	 * @return {@code true} if this {@code Promise} is still pending, {@code false} otherwise.
+	 * @see #isComplete()
+	 */
+	public boolean isPending() {
+		lock.lock();
+		try {
+			return state == State.PENDING;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	/**
+	 * Indicates whether this {@code Promise} has been successfully completed a value.
+	 *
+	 * @return {@code true} if this {@code Promise} is successful, {@code false} otherwise.
+	 */
+	public boolean isSuccess() {
+		lock.lock();
+		try {
+			return state == State.SUCCESS;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	/**
+	 * Indicates whether this {@code Promise} has been completed with an error.
+	 *
+	 * @return {@code true} if this {@code Promise} was completed with an error, {@code false} otherwise.
+	 */
+	public boolean isError() {
+		lock.lock();
+		try {
+			return state == State.FAILURE;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	/**
+	 * Block the calling thread, waiting for the completion of this {@code Promise}. A default timeout as specified in
+	 * Reactor's {@link Environment} properties using the key {@code reactor.await.defaultTimeout} is used. The default is
+	 * 30 seconds. If the promise is completed with an error a RuntimeException that wraps the error is thrown.
+	 *
+	 * @return the value of this {@code Promise} or {@code null} if the timeout is reached and the {@code Promise} has not
+	 * completed
+	 * @throws InterruptedException if the thread is interruped while awaiting completion
+	 * @throws RuntimeException     if the promise is completed with an error
+	 */
+	public T await() throws InterruptedException {
+		return await(defaultTimeout, TimeUnit.MILLISECONDS);
+	}
+
+	/**
+	 * Block the calling thread for the specified time, waiting for the completion of this {@code Promise}. If the promise
+	 * is completed with an error a RuntimeException that wraps the error is thrown.
+	 *
+	 * @param timeout the timeout value
+	 * @param unit    the {@link TimeUnit} of the timeout value
+	 * @return the value of this {@code Promise} or {@code null} if the timeout is reached and the {@code Promise} has not
+	 * completed
+	 * @throws InterruptedException if the thread is interruped while awaiting completion
+	 */
+	public T await(long timeout, TimeUnit unit) throws InterruptedException {
+		if (isPending()) {
+			flush();
+		}
+
+		if (!isPending()) {
+			return get();
+		}
+
+		lock.lock();
+		try {
+			hasBlockers = true;
+			if (timeout >= 0) {
+				long msTimeout = TimeUnit.MILLISECONDS.convert(timeout, unit);
+				long endTime = System.currentTimeMillis() + msTimeout;
+				while (state == State.PENDING && (System.currentTimeMillis()) < endTime) {
+					this.pendingCondition.await(200, TimeUnit.MILLISECONDS);
+				}
+			} else {
+				while (state == State.PENDING) {
+					this.pendingCondition.await(200, TimeUnit.MILLISECONDS);
+				}
+			}
+		} finally {
+			hasBlockers = false;
+			lock.unlock();
+		}
+
+		return get();
+	}
+
+	/**
+	 * Returns the value that completed this promise. Returns {@code null} if the promise has not been completed. If the
+	 * promise is completed with an error a RuntimeException that wraps the error is thrown.
+	 *
+	 * @return the value that completed the promise, or {@code null} if it has not been completed
+	 * @throws RuntimeException if the promise was completed with an error
+	 */
+	@Override
+	public T get() {
+		if (isPending()) {
+			flush();
+		}
+		lock.lock();
+		try {
+			if (state == State.SUCCESS) {
+				return value;
+			} else if (state == State.FAILURE) {
+				if (RuntimeException.class.isInstance(error)) {
+					throw (RuntimeException) error;
+				} else {
+					throw new RuntimeException(error);
+				}
+			} else {
+				return null;
+			}
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	/**
+	 * Return the error (if any) that has completed this {@code Promise}. Returns {@code null} if the promise has not been
+	 * completed, or was completed with a value.
+	 *
+	 * @return the error (if any)
+	 */
+	public Throwable reason() {
+		lock.lock();
+		try {
+			return error;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	public Promise<T> consume(@Nonnull Consumer<T> consumer) {
+		return (Promise<T>) super.consume(consumer);
+	}
+
+	@Override
+	public Promise<T> connect(@Nonnull final Composable<T> composable) {
+		return (Promise<T>) super.connect(composable);
+	}
+
+	@Override
+	public Promise<T> consume(@Nonnull Object key, @Nonnull Observable observable) {
+		return (Promise<T>) super.consume(key, observable);
+	}
+
+	@Override
+	public <V, C extends Composable<V>> Promise<V> mapMany(@Nonnull Function<T, C> fn) {
+		return (Promise<V>) super.mapMany(fn);
+	}
+
+	@Override
+	@SuppressWarnings("unchecked")
+	public <E extends Throwable> Promise<T> when(@Nonnull Class<E> exceptionType, @Nonnull Consumer<E> onError) {
+		lock.lock();
+		try {
+			if (state == State.FAILURE) {
+				Reactors.schedule(
+						new CallbackAction<E>(onError, getObservable(), null),
+						Event.wrap((E) error), getObservable());
+			} else {
+				super.when(exceptionType, onError);
+			}
+			return this;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	public <V> Promise<V> map(@Nonnull final Function<T, V> fn) {
+		Assert.notNull(fn, "Map function cannot be null.");
+		final Promise<V> d = newComposable();
+		add(new MapAction<T, V>(
+				fn,
+				d.getObservable(),
+				d.getAcceptKey(),
+				d.getError().getObject())).connectErrors(d);
+		return d;
+	}
+
+	@Override
+	public Promise<T> filter(@Nonnull final Predicate<T> p) {
+		return (Promise<T>) super.filter(p);
+	}
+
+	@Override
+	public Promise<T> filter(@Nonnull final Predicate<T> p, final Composable<T> elseComposable) {
+		final Promise<T> d = newComposable();
+		add(new FilterAction<T>(p, d.getObservable(), d.getAcceptKey(), d.getError().getObject(),
+				elseComposable != null ? elseComposable.getObservable() : null,
+				elseComposable != null ? elseComposable.getAcceptKey() : null)).connectErrors(d);
+
+		if(elseComposable != null){
+			consumeErrorAndFlush(elseComposable);
+		}
+		return d;
+	}
+
+	@Override
+	public Promise<Boolean> filter() {
+		return (Promise<Boolean>) super.filter();
+	}
+
+	@Override
+	public Promise<Boolean> filter(@Nonnull Composable<Boolean> elseComposable) {
+		return (Promise<Boolean>) super.filter(elseComposable);
+	}
+
+	@Override
+	public Promise<T> filter(@Nonnull Function<T, Boolean> fn) {
+		return (Promise<T>)super.filter(fn);
+	}
+
+	@Override
+	public Promise<T> merge(Composable<T>... composables) {
+		return (Promise<T>)super.merge(composables);
+	}
+
+	@Override
+	public Promise<T> timeout(long timeout) {
+		return (Promise<T>)super.timeout(timeout);
+	}
+
+	@Override
+	public Promise<T> timeout(long timeout, Timer timer) {
+		return (Promise<T>)super.timeout(timeout, timer);
+	}
+
+	@Override
+	public Promise<T> propagate(Supplier<T> supplier) {
+		return (Promise<T>)super.propagate(supplier);
+	}
+
+
+	@Override
+	public Promise<T> flush() {
+		return (Promise<T>) super.flush();
+	}
+
+	@Override
+	public Promise<T> add(Action<T> operation) {
+		lock.lock();
+		try {
+			if (state == State.SUCCESS) {
+				Reactors.schedule(operation, Event.wrap(value), getObservable());
+			} else if (state == State.FAILURE) {
+				Reactors.schedule(
+						new ConnectAction<Throwable>(operation.getObservable(), operation.getFailureKey(), null),
+						Event.wrap(error), getObservable());
+			} else {
+				super.add(operation);
+			}
+			return this;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	public Promise<T> consumeFlush(Flushable<?> action) {
+		lock.lock();
+		try {
+			if (state != State.PENDING) {
+				Reactors.schedule(
+						new FlushableAction(action, null, null),
+						Flushable.FLUSH_EVENT, getObservable());
+			} else {
+				super.consumeFlush(action);
+			}
+			return this;
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	@Override
+	public Promise<T> connectErrors(Composable<?> composable) {
+		return (Promise<T>)super.connectErrors(composable);
+	}
+
+	@Override
+	protected <V> Promise<V> newComposable() {
+		return new Promise<V>(null, getEnvironment(), this);
+	}
+
+	protected void errorAccepted(Throwable error) {
+		lock.lock();
+		try {
+			assertPending();
+			this.error = error;
+			this.state = State.FAILURE;
+			if (hasBlockers) {
+				pendingCondition.signalAll();
+				hasBlockers = false;
+			}
+		} finally {
+			lock.unlock();
+		}
+		getObservable().notify(getFlush().getObject(), Event.wrap(this));
+
+	}
+
+	protected void valueAccepted(T value) {
+		lock.lock();
+		try {
+			assertPending();
+			this.value = value;
+			this.state = State.SUCCESS;
+			if (hasBlockers) {
+				pendingCondition.signalAll();
+				hasBlockers = false;
+			}
+		} finally {
+			lock.unlock();
+		}
+		getObservable().notify(getFlush().getObject(), Event.wrap(this));
+	}
+
+	@Override
+	void notifyValue(Event<T> value) {
+		lock.lock();
+		try {
+			assertPending();
+		} finally {
+			lock.unlock();
+		}
+		super.notifyValue(value);
+	}
+
+	@Override
+	void notifyError(Throwable error) {
+		lock.lock();
+		try {
+			assertPending();
+		} finally {
+			lock.unlock();
+		}
+		super.notifyError(error);
+	}
+
+	@Override
+	void notifyFlush() {
+		boolean flush = false;
+		lock.lock();
+		try {
+			flush = state == State.PENDING;
+		} finally {
+			lock.unlock();
+		}
+		if(flush){
+			super.notifyFlush();
+		}
+	}
+
+	private void assertPending() {
+		Assert.state(isPending(), "Promise has already completed. ");
+	}
+
+	private enum State {
+		PENDING, SUCCESS, FAILURE;
+	}
+
+	@Override
+	public String toString() {
+		lock.lock();
+		try {
+			return "Promise{" +
+					"value=" + value +
+					", error=" + error +
+					'}';
+		} finally {
+			lock.unlock();
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/composable/Stream.java b/reactor-core/src/main/java/reactor/core/composable/Stream.java
new file mode 100644
index 0000000..de60b05
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/Stream.java
@@ -0,0 +1,745 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.composable;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.action.*;
+import reactor.core.composable.spec.DeferredStreamSpec;
+import reactor.event.dispatch.Dispatcher;
+import reactor.event.selector.Selector;
+import reactor.function.*;
+import reactor.function.support.Tap;
+import reactor.timer.Timer;
+import reactor.tuple.Tuple2;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@code Stream} is a stateless event processor that provides methods for attaching {@link
+ * Consumer Consumers} to consume the values passing through the stream. Some methods like
+ * {@link #map(reactor.function.Function)}, {@link #filter(reactor.function.Predicate)},
+ * {@link #first()}, {@link #last()}, and {@link #reduce(reactor.function.Function, Object)}
+ * return new {@code Stream Streams} whose values are the result of transformation functions,
+ * filtering, and the like.
+ * <p>
+ * Typically, new {@code Stream Streams} aren't created directly. To create a {@code Stream},
+ * create a {@link DeferredStreamSpec} and configure it with the appropriate {@link Environment},
+ * {@link Dispatcher}, and other settings, then call {@link Deferred#compose()}, which will
+ * return a {@code Stream} that handles the values passed into the {@link Deferred}.
+ * </p>
+ *
+ * @param <T> the type of the values in the stream
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ * @author Stephane Maldini
+ */
+public class Stream<T> extends Composable<T> {
+
+	protected final int batchSize;
+
+	/**
+	 * Create a new Stream that will use the {@link Observable} to pass its values to registered
+	 * handlers.
+	 * <p>
+	 * The stream will batch values into batches of the given
+	 * {@code batchSize}, affecting the values that are passed to the {@link #first()} and {@link
+	 * #last()} substreams. A size of {@code -1} indicates that the stream should not be batched.
+	 * </p>
+	 * Once all event handler have been registered, {@link #flush} must be called to pass the initial values
+	 * to those handlers. The stream will accept errors from the given {@code parent}.
+	 *
+	 * @param observable The observable used to drive event handlers
+	 * @param batchSize  The size of the batches, or {@code -1} for no batching
+	 * @param parent     The stream's parent. May be {@code null}
+	 */
+	public Stream(@Nullable final Observable observable,
+	              int batchSize,
+	              @Nullable final Composable<?> parent,
+	              @Nullable final Environment environment) {
+		this(observable, batchSize, parent, null, environment);
+	}
+
+	/**
+	 * Create a new Stream that will use the {@link Observable} to pass its values to registered
+	 * handlers.
+	 * <p>
+	 * The stream will batch values into batches of the given {@code batchSize}, affecting the values that are passed to
+	 * the {@link #first()} and {@link #last()} substreams. A size of {@code -1} indicates that the stream should not be
+	 * batched.
+	 * </p>
+	 * Once all event handler have been registered, {@link #flush} must be called to pass the initial values
+	 * to those handlers. The stream will accept errors from the given {@code parent}.
+	 *
+	 * @param observable     The observable used to drive event handlers
+	 * @param batchSize      The size of the batches, or {@code -1} for no batching
+	 * @param parent         The stream's parent. May be {@code null}
+	 * @param acceptSelector The tuple Selector/Key to accept values on this observable. May be {@code null}
+	 */
+	public Stream(@Nullable final Observable observable,
+	              int batchSize,
+	              @Nullable final Composable<?> parent, Tuple2<Selector, Object> acceptSelector,
+	              @Nullable final Environment environment) {
+		super(observable, parent, acceptSelector, environment);
+		this.batchSize = batchSize;
+	}
+
+	@Override
+	public Stream<T> consume(@Nonnull Consumer<T> consumer) {
+		return (Stream<T>) super.consume(consumer);
+	}
+
+	@Override
+	public Stream<T> connect(@Nonnull Composable<T> consumer) {
+		return (Stream<T>) super.connect(consumer);
+	}
+
+	@Override
+	public Stream<T> consume(@Nonnull Object key, @Nonnull Observable observable) {
+		return (Stream<T>) super.consume(key, observable);
+	}
+
+	@Override
+	public Stream<T> flush() {
+		return (Stream<T>) super.flush();
+	}
+
+	@Override
+	public <E extends Throwable> Stream<T> when(@Nonnull Class<E> exceptionType, @Nonnull Consumer<E> onError) {
+		return (Stream<T>) super.when(exceptionType, onError);
+	}
+
+	@Override
+	public <V> Stream<V> map(@Nonnull Function<T, V> fn) {
+		return (Stream<V>) super.map(fn);
+	}
+
+	@Override
+	public <V, C extends Composable<V>> Stream<V> mapMany(@Nonnull Function<T, C> fn) {
+		return (Stream<V>) super.mapMany(fn);
+	}
+
+	@Override
+	public Stream<Boolean> filter() {
+		return (Stream<Boolean>) super.filter();
+	}
+
+	@Override
+	public Stream<Boolean> filter(@Nonnull Composable<Boolean> elseComposable) {
+		return (Stream<Boolean>) super.filter(elseComposable);
+	}
+
+	@Override
+	public Stream<T> filter(@Nonnull Function<T, Boolean> p) {
+		return (Stream<T>) super.filter(p);
+	}
+
+	@Override
+	public Stream<T> filter(@Nonnull Predicate<T> p) {
+		return (Stream<T>) super.filter(p);
+	}
+
+	@Override
+	public Stream<T> filter(@Nonnull Predicate<T> p, Composable<T> elseComposable) {
+		return (Stream<T>) super.filter(p, elseComposable);
+	}
+
+	@Override
+	public Stream<T> connectValues(@Nonnull Composable<T> composable) {
+		return (Stream<T>) super.connectValues(composable);
+	}
+
+	@Override
+	public Stream<T> timeout(long timeout, Timer timer) {
+		return (Stream<T>) super.timeout(timeout, timer);
+	}
+
+	@Override
+	public Stream<T> timeout(long timeout) {
+		return (Stream<T>) super.timeout(timeout);
+	}
+
+	@Override
+	public Stream<T> merge(Composable<T>... composables) {
+		return (Stream<T>) super.merge(composables);
+	}
+
+	@Override
+	public Stream<T> consumeFlush(@Nonnull Flushable<?> consumer) {
+		return (Stream<T>) super.consumeFlush(consumer);
+	}
+
+	@Override
+	public Stream<T> connectErrors(Composable<?> composable) {
+		return (Stream<T>) super.connectErrors(composable);
+	}
+
+	/**
+	 * Create a new {@code Stream} whose values will be each iterated item from {@param iterable}
+	 * Every time flush is triggered,  {@param iterable} is drained.
+	 *
+	 * @param iterable the iterable to drain
+	 * @return a new {@code Stream} whose values are the iterated one on flush
+	 * @since 1.1
+	 */
+	public Stream<T> propagate(Iterable<T> iterable) {
+		final Stream<T> d = newComposable(batchSize);
+
+		consumeFlush(new ForEachAction<T>(iterable,
+		                                  d.getObservable(),
+		                                  d.getAcceptKey(),
+		                                  d.getError().getObject(),
+		                                  d.getFlush().getObject())
+		).connectErrors(d);
+		return d;
+	}
+
+	@Override
+	public Stream<T> propagate(Supplier<T> supplier) {
+		return (Stream<T>) super.propagate(supplier);
+	}
+
+	/**
+	 * Consume values and trigger flush when {@param predicate} matches.
+	 *
+	 * @param predicate the test returning true to trigger flush
+	 * @return the current Stream
+	 * @since 1.1
+	 */
+	public Stream<T> flushWhen(Predicate<T> predicate) {
+		add(new WhenAction<T>(predicate,
+		                      getObservable(),
+		                      getFlush().getObject(),
+		                      getError().getObject()));
+		return this;
+	}
+
+	/**
+	 * Create a new {@code Stream} whose values will be only the first value of each batch. Requires a {@code batchSize}
+	 * to
+	 * have been set.
+	 * <p>
+	 * When a new batch is triggered, the first value of that next batch will be pushed into this {@code Stream}.
+	 *
+	 * @return a new {@code Stream} whose values are the first value of each batch
+	 */
+	public Stream<T> first() {
+		return first(batchSize);
+	}
+
+	/**
+	 * Create a new {@code Stream} whose values will be only the first value of each batch. Requires a {@code batchSize}
+	 * to
+	 * have been set.
+	 * <p>
+	 * When a new batch is triggered, the first value of that next batch will be pushed into this {@code Stream}.
+	 *
+	 * @param batchSize the batch size to use
+	 * @return a new {@code Stream} whose values are the first value of each batch)
+	 */
+	public Stream<T> first(int batchSize) {
+		Assert.state(batchSize > 0, "Cannot first() an unbounded Stream. Try extracting a batch first.");
+		final Stream<T> d = newComposable(batchSize);
+		add(new BatchAction<T>(batchSize,
+		                       d.getObservable(),
+		                       null,
+		                       d.getError().getObject(),
+		                       null,
+		                       d.getAcceptKey())).connectErrors(d);
+		return d;
+	}
+
+	/**
+	 * Create a new {@code Stream} whose values will be only the last value of each batch. Requires a {@code batchSize}
+	 *
+	 * @return a new {@code Stream} whose values are the last value of each batch
+	 */
+	public Stream<T> last() {
+		return last(batchSize);
+	}
+
+	/**
+	 * Create a new {@code Stream} whose values will be only the last value of each batch. Requires a {@code batchSize}
+	 *
+	 * @param batchSize the batch size to use
+	 * @return a new {@code Stream} whose values are the last value of each batch
+	 */
+	public Stream<T> last(int batchSize) {
+		Assert.state(batchSize > 0, "Cannot last() an unbounded Stream. Try extracting a batch first.");
+		final Stream<T> d = newComposable(batchSize);
+		add(new BatchAction<T>(batchSize,
+		                       d.getObservable(),
+		                       null,
+		                       d.getError().getObject(),
+		                       d.getAcceptKey(),
+		                       null)).connectErrors(d);
+		return d;
+	}
+
+
+	/**
+	 * Create a new {@code Stream} that filters out consecutive equals values.
+	 *
+	 * @return a new {@code Stream} whose values are the last value of each batch
+	 * @since 1.1
+	 */
+	public Stream<T> distinct() {
+		final Stream<T> d = newComposable(batchSize);
+		add(new DistinctAction<T>(
+				d.getObservable(),
+				d.getAcceptKey(),
+				d.getError().getObject()
+		)).consumeErrorAndFlush(d);
+		return d;
+	}
+
+
+	/**
+	 * Create a new {@code Stream} whose values will be each element E of any Iterable<E> flowing this Stream
+	 * <p>
+	 * When a new batch is triggered, the last value of that next batch will be pushed into this {@code Stream}.
+	 *
+	 * @return a new {@code Stream} whose values result from the iterable input
+	 * @since 1.1
+	 */
+	public <V> Stream<V> split() {
+		return split(batchSize);
+	}
+
+	/**
+	 * Create a new {@code Stream} whose values will be each element E of any Iterable<E> flowing this Stream
+	 * <p>
+	 * When a new batch is triggered, the last value of that next batch will be pushed into this {@code Stream}.
+	 *
+	 * @param batchSize the batch size to use
+	 * @return a new {@code Stream} whose values result from the iterable input
+	 * @since 1.1
+	 */
+	public <V> Stream<V> split(int batchSize) {
+		final Stream<V> d = newComposable(batchSize);
+		getObservable().on(getAcceptSelector(), new ForEachAction<T>(d.getObservable(),
+		                                                             d.getAcceptKey(),
+		                                                             d.getError().getObject(),
+		                                                             d.getFlush().getObject()));
+		connectErrors(d);
+		return d;
+	}
+
+	/**
+	 * Create a {@link Tap} that maintains a reference to the last value seen by this {@code Stream}. The {@link Tap} is
+	 * continually updated when new values pass through the {@code Stream}.
+	 *
+	 * @return the new {@link Tap}
+	 * @see Consumer
+	 */
+	public Tap<T> tap() {
+		final Tap<T> tap = new Tap<T>();
+		consume(tap);
+		return tap;
+	}
+
+
+	/**
+	 * Use {@link reactor.core.Observable#batchNotify(Object)} to group dispatching by the current batch size.
+	 * When a new batch is triggered, the last value of that next batch will be pushed into this {@code Stream}.
+	 *
+	 * @return a new {@code Stream} whose values result from the iterable input
+	 * @since 1.1
+	 */
+	public Stream<T> buffer() {
+		return buffer(batchSize);
+	}
+
+	/**
+	 * Use {@link reactor.core.Observable#batchNotify(Object)} to group dispatch by the passed {@param
+	 * batchSize}.
+	 * When a new batch is triggered, the last value of that next batch will be pushed into this {@code Stream}.
+	 *
+	 * @param batchSize the batch size to use
+	 * @return a new {@code Stream} whose values result from the iterable input
+	 * @since 1.1
+	 */
+	public Stream<T> buffer(int batchSize) {
+		final Stream<T> d = newComposable(batchSize);
+		add(new BufferAction<T>(batchSize,
+		                        d.getObservable(),
+		                        d.getAcceptKey(),
+		                        d.getError().getObject(),
+		                        d.getFlush().getObject())
+		);
+		return d;
+	}
+
+	/**
+	 * Collect incoming values into a {@link List} that will be pushed separately into the returned {@code Stream}
+	 * every time {@code
+	 * batchSize} has been reached. All errors are also captured until current batchSize or flush is called.
+	 *
+	 * @return a new {@code Stream} whose values of all values in this batch
+	 * @since 1.1
+	 */
+	public Stream<T> bufferWithErrors() {
+		return bufferWithErrors(batchSize);
+	}
+
+	/**
+	 * Collect incoming values into a {@link List} that will be pushed separately into the returned {@code Stream} every
+	 * time {@code
+	 * batchSize} has been reached. All errors are also captured until batchSize or flush is called.
+	 *
+	 * @param batchSize the collected size
+	 * @return a new {@code Stream} whose values are all values in this batch
+	 * @since 1.1
+	 */
+	public Stream<T> bufferWithErrors(int batchSize) {
+		final Stream<T> d = newComposable(batchSize);
+		final BufferAction<Throwable> errorBuffer = new BufferAction<Throwable>(batchSize,
+		                                                                        d.getObservable(),
+		                                                                        d.getError().getObject(),
+		                                                                        null,
+		                                                                        null);
+
+		getObservable().on(getError(), errorBuffer);
+		consumeFlush(errorBuffer);
+
+		add(new BufferAction<T>(batchSize,
+		                        d.getObservable(),
+		                        d.getAcceptKey(),
+		                        d.getError().getObject(),
+		                        d.getFlush().getObject())
+		);
+
+
+		return d;
+	}
+
+	/**
+	 * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every time {@code
+	 * batchSize} or flush is triggered has been reached.
+	 *
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this batch
+	 */
+	public Stream<List<T>> collect() {
+		return collect(batchSize);
+	}
+
+	/**
+	 * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every time {@code
+	 * batchSize} has been reached.
+	 *
+	 * @param batchSize the collected size
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this batch
+	 */
+	public Stream<List<T>> collect(int batchSize) {
+		final Stream<List<T>> d = newComposable(1);
+
+		add(new CollectAction<T>(batchSize,
+		                         d.getObservable(),
+		                         d.getAcceptKey(),
+		                         d.getError().getObject())).connectErrors(d);
+
+		return d;
+	}
+
+	/**
+	 * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every specified
+	 * time from the {@param period} in milliseconds. The window runs on a timer from the stream {@link
+	 * this#environment}.
+	 *
+	 * @param period the time period when each window close and flush the attached consumer
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this window
+	 * @since 1.1
+	 */
+	public Stream<List<T>> window(int period) {
+		return window(period, TimeUnit.MILLISECONDS);
+	}
+
+	/**
+	 * Collect incoming values into an internal array, providing a {@link List} that will be pushed into the returned
+	 * {@code Stream} every specified {@param period} in milliseconds. The window runs on a timer from the stream {@link
+	 * this#environment}. After accepting {@param backlog} of items, every old item will be dropped. Resulting {@link
+	 * List} will be at most {@param backlog} items long.
+	 *
+	 * @param period  the time period when each window close and flush the attached consumer
+	 * @param backlog maximum amount of items to keep
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this window
+	 * @since 1.1
+	 */
+	public Stream<List<T>> movingWindow(int period, int backlog) {
+		return movingWindow(period, TimeUnit.MILLISECONDS, backlog);
+	}
+
+	/**
+	 * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every specified
+	 * time from the {@param period} and a {@param timeUnit}. The window
+	 * runs on a timer from the stream {@link this#environment}.
+	 *
+	 * @param period   the time period when each window close and flush the attached consumer
+	 * @param timeUnit the time unit used for the period
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this window
+	 * @since 1.1
+	 */
+	public Stream<List<T>> window(int period, TimeUnit timeUnit) {
+		return window(period, timeUnit, 0);
+	}
+
+	/**
+	 * Collect incoming values into an internal array, providing a {@link List} that will be pushed into the returned
+	 * {@code Stream} every specified time from the {@param period} and a {@param timeUnit}.
+	 *
+	 * @param period   the time period when each window close and flush the attached consumer
+	 * @param timeUnit the time unit used for the period
+	 * @param backlog  maximum amount of items to keep
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this window
+	 * @since 1.1
+	 */
+	public Stream<List<T>> movingWindow(int period, TimeUnit timeUnit, int backlog) {
+		return movingWindow(period, timeUnit, 0, backlog);
+	}
+
+	/**
+	 * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every specified
+	 * time from the {@param period}, {@param timeUnit} after an initial {@param delay} in milliseconds. The window
+	 * runs on a timer from the stream {@link this#environment}.
+	 *
+	 * @param period   the time period when each window close and flush the attached consumer
+	 * @param timeUnit the time unit used for the period
+	 * @param delay    the initial delay in milliseconds
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this window
+	 * @since 1.1
+	 */
+	public Stream<List<T>> window(int period, TimeUnit timeUnit, int delay) {
+		Assert.state(getEnvironment() != null,
+		             "Cannot use default timer as no environment has been provided to this Stream");
+		return window(period, timeUnit, delay, getEnvironment().getRootTimer());
+	}
+
+	/**
+	 * Collect incoming values into an internal array, providing a {@link List} that will be pushed into the returned
+	 * {@code Stream} every specified time from the {@param period} and a {@param timeUnit} after an initial {@param
+	 * delay}
+	 * in milliseconds.
+	 *
+	 * @param period   the time period when each window close and flush the attached consumer
+	 * @param timeUnit the time unit used for the period
+	 * @param delay    the initial delay in milliseconds
+	 * @param backlog  maximum amount of items to keep
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this window
+	 * @since 1.1
+	 */
+	public Stream<List<T>> movingWindow(int period, TimeUnit timeUnit, int delay, int backlog) {
+		Assert.state(getEnvironment() != null,
+		             "Cannot use default timer as no environment has been provided to this Stream");
+		return movingWindow(period, timeUnit, delay, backlog, getEnvironment().getRootTimer());
+	}
+
+	/**
+	 * Collect incoming values into a {@link List} that will be pushed into the returned {@code Stream} every specified
+	 * time from the {@param period}, {@param timeUnit} after an initial {@param delay} in milliseconds. The window
+	 * runs on a supplied {@param timer}.
+	 *
+	 * @param period   the time period when each window close and flush the attached consumer
+	 * @param timeUnit the time unit used for the period
+	 * @param delay    the initial delay in milliseconds
+	 * @param timer    the reactor timer to run the window on
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this window
+	 * @since 1.1
+	 */
+	public Stream<List<T>> window(int period, TimeUnit timeUnit, int delay, Timer timer) {
+		Assert.state(timer != null, "Timer must be supplied");
+		final Stream<List<T>> d = newComposable(1);
+
+		add(new WindowAction<T>(d.getObservable(),
+		                        d.getAcceptKey(),
+		                        d.getError().getObject(),
+		                        timer,
+		                        period, timeUnit, delay)).connectErrors(d);
+
+		return d;
+	}
+
+	/**
+	 * Collect incoming values into an internal array, providing a {@link List} that will be pushed into the returned
+	 * {@code Stream} every specified time from the {@param period} and a {@param timeUnit} after an initial {@param
+	 * delay}
+	 * in milliseconds.
+	 *
+	 * @param period   the time period when each window close and flush the attached consumer
+	 * @param timeUnit the time unit used for the period
+	 * @param delay    the initial delay in milliseconds
+	 * @param backlog  maximum amount of items to keep
+	 * @param timer    the reactor timer to run the window on
+	 * @return a new {@code Stream} whose values are a {@link List} of all values in this window
+	 * @since 1.1
+	 */
+	public Stream<List<T>> movingWindow(int period, TimeUnit timeUnit, int delay, int backlog, Timer timer) {
+		Assert.state(timer != null, "Timer must be supplied");
+		final Stream<List<T>> d = newComposable(1);
+
+		add(new MovingWindowAction<T>(d.getObservable(),
+		                              d.getAcceptKey(),
+		                              d.getError().getObject(),
+		                              timer,
+		                              period, timeUnit, delay, backlog)).connectErrors(d);
+
+		return d;
+	}
+
+	/**
+	 * Reduce the values passing through this {@code Stream} into an object {@code A}. The given initial object will be
+	 * passed to the function's {@link Tuple2} argument.
+	 *
+	 * @param fn      the reduce function
+	 * @param initial the initial argument to pass to the reduce function
+	 * @param <A>     the type of the reduced object
+	 * @return a new {@code Stream} whose values contain only the reduced objects
+	 */
+	public <A> Stream<A> reduce(@Nonnull Function<Tuple2<T, A>, A> fn, A initial) {
+		return reduce(fn, Functions.supplier(initial), batchSize);
+	}
+
+	/**
+	 * Reduce the values passing through this {@code Stream} into an object {@code A}. The given {@link Supplier} will be
+	 * used to produce initial accumulator objects either on the first reduce call, in the case of an unbounded {@code
+	 * Stream}, or on the first value of each batch, if a {@code batchSize} is set.
+	 * <p>
+	 * In an unbounded {@code Stream}, the accumulated value will be published on the returned {@code Stream} on flush
+	 * only. But when a {@code batchSize} has been, the accumulated
+	 * value will only be published on the new {@code Stream} at the end of each batch. On the next value (the first of
+	 * the next batch), the {@link Supplier} is called again for a new accumulator object and the reduce starts over with
+	 * a new accumulator.
+	 *
+	 * @param fn           the reduce function
+	 * @param accumulators the {@link Supplier} that will provide accumulators
+	 * @param batchSize    the batch size to use
+	 * @param <A>          the type of the reduced object
+	 * @return a new {@code Stream} whose values contain only the reduced objects
+	 */
+	public <A> Stream<A> reduce(@Nonnull final Function<Tuple2<T, A>, A> fn, @Nullable final Supplier<A> accumulators,
+	                            final int batchSize
+	) {
+		final Stream<A> stream = newComposable(1);
+		add(new ReduceAction<T, A>(batchSize,
+		                           accumulators,
+		                           fn,
+		                           stream.getObservable(),
+		                           stream.getAcceptKey(),
+		                           stream.getError().getObject())).connectErrors(stream);
+
+		return stream;
+	}
+
+	/**
+	 * Reduce the values passing through this {@code Stream} into an object {@code A}.
+	 *
+	 * @param fn  the reduce function
+	 * @param <A> the type of the reduced object
+	 * @return a new {@code Stream} whose values contain only the reduced objects
+	 */
+	public <A> Stream<A> reduce(@Nonnull final Function<Tuple2<T, A>, A> fn) {
+		return reduce(fn, null, batchSize);
+	}
+
+	/**
+	 * Scan the values passing through this {@code Stream} into an object {@code A}. The given initial object will be
+	 * passed to the function's {@link Tuple2} argument. Behave like Reduce but triggers downstream Stream for every
+	 * transformation.
+	 *
+	 * @param fn      the scan function
+	 * @param initial the initial argument to pass to the reduce function
+	 * @param <A>     the type of the reduced object
+	 * @return a new {@code Stream} whose values contain only the reduced objects
+	 * @since 1.1
+	 */
+	public <A> Stream<A> scan(@Nonnull Function<Tuple2<T, A>, A> fn, A initial) {
+		return scan(fn, Functions.supplier(initial));
+	}
+
+	/**
+	 * Scan the values passing through this {@code Stream} into an object {@code A}. The given {@link Supplier} will be
+	 * used to produce initial accumulator objects either on the first reduce call, in the case of an unbounded {@code
+	 * Stream}, or on the first value of each batch, if a {@code batchSize} is set.
+	 * <p>
+	 * The accumulated value will be published on the returned {@code Stream} every time
+	 * a
+	 * value is accepted.
+	 *
+	 * @param fn           the scan function
+	 * @param accumulators the {@link Supplier} that will provide accumulators
+	 * @param <A>          the type of the reduced object
+	 * @return a new {@code Stream} whose values contain only the reduced objects
+	 * @since 1.1
+	 */
+	public <A> Stream<A> scan(@Nonnull final Function<Tuple2<T, A>, A> fn, @Nullable final Supplier<A> accumulators) {
+		final Stream<A> stream = newComposable(1);
+		add(new ScanAction<T, A>(accumulators,
+		                         fn,
+		                         stream.getObservable(),
+		                         stream.getAcceptKey(),
+		                         stream.getError().getObject())).consumeErrorAndFlush(stream);
+
+		return stream;
+	}
+
+	/**
+	 * Scan the values passing through this {@code Stream} into an object {@code A}.
+	 *
+	 * @param fn  the reduce function
+	 * @param <A> the type of the reduced object
+	 * @return a new {@code Stream} whose values contain only the reduced objects
+	 * @since 1.1
+	 */
+	public <A> Stream<A> scan(@Nonnull final Function<Tuple2<T, A>, A> fn) {
+		return scan(fn, (Supplier<A>) null);
+	}
+
+	/**
+	 * Count accepted events for each batch (every flush) and pass each accumulated long to the {@param stream}.
+	 *
+	 * @param stream the stream to consume accumulated number of accepted event between 2 flushes
+	 * @since 1.1
+	 */
+	public Stream<T> count(Stream<Long> stream) {
+		add(new CountAction<T>(stream.getObservable(), stream.getAcceptKey(), stream.getError().getObject()));
+		return this;
+	}
+
+
+	@Override
+	protected <V> Stream<V> newComposable() {
+		return newComposable(batchSize);
+	}
+
+	protected <V> Stream<V> newComposable(int batchSize) {
+		return new Stream<V>(null,
+		                     batchSize,
+		                     this,
+		                     getEnvironment());
+	}
+
+	BufferAction<T> bufferConsumer(int batchSize) {
+		BufferAction<T> bufferAction = new BufferAction<T>(batchSize, getObservable(),
+		                                                   getAcceptKey(), getError(), getFlush());
+		consumeFlush(bufferAction);
+		return bufferAction;
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/composable/package-info.java b/reactor-core/src/main/java/reactor/core/composable/package-info.java
new file mode 100644
index 0000000..a1a1cf3
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Composables are reactive components that provide a composition API for reacting to events rather than by using
+ * {@link reactor.event.selector.Selector Selectors} and {@link reactor.core.Reactor Reactors} directly.
+ */
+package reactor.core.composable;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/composable/spec/ComposableSpec.java b/reactor-core/src/main/java/reactor/core/composable/spec/ComposableSpec.java
new file mode 100644
index 0000000..ed3b2b7
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/spec/ComposableSpec.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.composable.spec;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.Reactor;
+import reactor.core.spec.support.DispatcherComponentSpec;
+import reactor.event.dispatch.Dispatcher;
+import reactor.event.selector.Selector;
+import reactor.tuple.Tuple2;
+
+/**
+ * A helper class for specifying a bounded {@link reactor.core.composable.Composable}.
+ *
+ * @param <SPEC>   The ComposableSpec subclass
+ * @param <TARGET> The type that this spec will create
+ * @author Stephane Maldini
+ */
+public abstract class ComposableSpec<SPEC extends ComposableSpec<SPEC, TARGET>, TARGET> extends DispatcherComponentSpec<SPEC,
+		TARGET> {
+
+	private Observable               observable;
+	private Tuple2<Selector, Object> acceptSelector;
+
+
+	/**
+	 * Configures the Composable to reuse an explicit selector/key rather than the internal anonymous generated one.
+	 *
+	 * @param acceptSelector The selector tuple to listen/publish to
+	 * @return {@code this}
+	 * @since 1.1
+	 */
+	@SuppressWarnings("unchecked")
+	SPEC acceptSelector(final Tuple2<Selector, Object> acceptSelector) {
+		this.acceptSelector = acceptSelector;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the Composable to reuse an explicit observable rather than the internal anonymous generated one.
+	 *
+	 * @param observable The observable to listen/publish to
+	 * @return {@code this}
+	 * @since 1.1
+	 */
+	@SuppressWarnings("unchecked")
+	SPEC observable(final Observable observable) {
+		this.observable = observable;
+		return (SPEC) this;
+	}
+
+
+	@Override
+	protected TARGET configure(final Dispatcher dispatcher, Environment env) {
+		if (observable == null) {
+			observable = new Reactor(dispatcher);
+		}
+		return createComposable(env, observable, acceptSelector);
+	}
+
+	protected abstract TARGET createComposable(Environment env, Observable observable, Tuple2<Selector, Object> accept);
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/spec/DeferredPromiseSpec.java b/reactor-core/src/main/java/reactor/core/composable/spec/DeferredPromiseSpec.java
new file mode 100644
index 0000000..54f8509
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/spec/DeferredPromiseSpec.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.composable.spec;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.composable.Composable;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.event.selector.Selector;
+import reactor.tuple.Tuple2;
+
+/**
+ * A helper class for specifying a {@link Deferred} {@link Promise}.
+ *
+ * @param <T> The type of the value that the promise will accept
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public final class DeferredPromiseSpec<T> extends ComposableSpec<DeferredPromiseSpec<T>, Deferred<T, Promise<T>>> {
+
+	private Composable<?> parent;
+
+	/**
+	 * Configures the promise to have the given {@code parent}
+	 *
+	 * @param parent The parent for the promise that's being configured
+	 *
+	 * @return {@code this}
+	 */
+	public DeferredPromiseSpec<T> link(Composable<?> parent) {
+		this.parent = parent;
+		return this;
+	}
+
+	@Override
+	protected Deferred<T, Promise<T>> createComposable(Environment env, Observable observable,
+	                                                   Tuple2<Selector, Object> accept) {
+		return new Deferred<T, Promise<T>>(new Promise<T>(observable, env, parent));
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/spec/DeferredStreamSpec.java b/reactor-core/src/main/java/reactor/core/composable/spec/DeferredStreamSpec.java
new file mode 100644
index 0000000..7242e95
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/spec/DeferredStreamSpec.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.composable.spec;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.action.BufferAction;
+import reactor.core.composable.Composable;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Stream;
+import reactor.event.Event;
+import reactor.event.selector.Selector;
+import reactor.tuple.Tuple2;
+
+/**
+ * A helper class for specifying a {@link Deferred} {@link Stream}.
+ *
+ * @param <T> The type of values that the stream will contain
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public final class DeferredStreamSpec<T> extends ComposableSpec<DeferredStreamSpec<T>, Deferred<T, Stream<T>>> {
+
+	private int batchSize = -1;
+
+	/**
+	 * Configures the stream to have the given {@code batchSize}. A value of {@code -1}, which
+	 * is the default configuration, configures the stream to not be batched.
+	 *
+	 * @param batchSize The batch size of the stream
+	 * @return {@code this}
+	 */
+	public DeferredStreamSpec<T> batchSize(int batchSize) {
+		this.batchSize = batchSize;
+		return this;
+	}
+
+	@Override
+	protected Deferred<T, Stream<T>> createComposable(Environment env, Observable observable,
+	                                                  Tuple2<Selector, Object> accept) {
+		Stream<T> stream =
+				new Stream<T>(observable, batchSize, null, accept, env);
+		if (batchSize > 1) {
+			return new BatchStreamDeferred<T>(stream, batchSize);
+		} else {
+			return new Deferred<T, Stream<T>>(stream);
+		}
+
+	}
+
+	private static class BatchStreamDeferred<T> extends Deferred<T, Stream<T>> {
+		private final BufferAction<T> consumer;
+
+		public BatchStreamDeferred(Stream<T> stream, int batchSize) {
+			super(stream);
+			consumer = batcher(batchSize);
+		}
+
+		@Override
+		public void acceptEvent(Event<T> value) {
+			consumer.accept(value);
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/spec/PromiseSpec.java b/reactor-core/src/main/java/reactor/core/composable/spec/PromiseSpec.java
new file mode 100644
index 0000000..7c29d7d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/spec/PromiseSpec.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.composable.spec;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.composable.Promise;
+import reactor.event.selector.Selector;
+import reactor.function.Supplier;
+import reactor.tuple.Tuple2;
+import reactor.util.Assert;
+
+/**
+ * A helper class for specifying a {@link Promise}.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ *
+ * @param <T> the type of the value that the Promise will contain
+ */
+public final class PromiseSpec<T> extends ComposableSpec<PromiseSpec<T>, Promise<T>> {
+
+	private T             value;
+	private Supplier<T>   valueSupplier;
+	private Throwable     error;
+
+	/**
+	 * Configures the promise to have been successfully completed with the given {@code value}.
+	 *
+	 * @param value The value for the promise to contain
+	 *
+	 * @return {@code this}
+	 */
+	public PromiseSpec<T> success(T value) {
+		Assert.isNull(error, "Cannot set both a value and an error. Use one or the other.");
+		Assert.isNull(valueSupplier, "Cannot set both a value and a Supplier. Use one or the other.");
+		this.value = value;
+		return this;
+	}
+
+	/**
+	 * Configures the promise to have been successfully completed with the value from the given
+	 * {@code valueSupplier}.
+	 *
+	 * @param valueSupplier The supplier of the value for the promise to contain
+	 *
+	 * @return {@code this}
+	 */
+	public PromiseSpec<T> supply(Supplier<T> valueSupplier) {
+		Assert.isNull(error, "Cannot set both an error and a Supplier. Use one or the other.");
+		Assert.isNull(value, "Cannot set both a value and a Supplier. Use one or the other.");
+		this.valueSupplier = valueSupplier;
+		return this;
+	}
+
+	/**
+	 * Configures the promise to have been completed with the given {@code error}.
+	 *
+	 * @param error The error to be held by the Promise
+	 *
+	 * @return {@code this}
+	 */
+	public PromiseSpec<T> error(Throwable error) {
+		Assert.isNull(value, "Cannot set both a value and an error. Use one or the other.");
+		Assert.isNull(valueSupplier, "Cannot set both an error and a Supplier. Use one or the other.");
+		this.error = error;
+		return this;
+	}
+
+	@Override
+	protected Promise<T> createComposable(Environment env, Observable observable,
+	                                      Tuple2<Selector, Object> accept) {
+		if (value != null) {
+			return new Promise<T>(value, observable, env);
+		} else if (valueSupplier != null) {
+			return new Promise<T>(observable, env, null).propagate(valueSupplier);
+		} else if (error != null) {
+			return new Promise<T>(error, observable, env);
+		} else {
+			throw new IllegalStateException("A success value/supplier or error reason must be provided. Use " +
+				DeferredPromiseSpec.class.getSimpleName() + " to create a deferred promise");
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/spec/Promises.java b/reactor-core/src/main/java/reactor/core/composable/spec/Promises.java
new file mode 100644
index 0000000..69902ff
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/spec/Promises.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.composable.spec;
+
+import com.gs.collections.impl.list.mutable.FastList;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+import reactor.core.composable.Composable;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.event.dispatch.Dispatcher;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper methods for creating {@link Deferred} instances, backed by a {@link Promise}.
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ */
+public abstract class Promises {
+
+	/**
+	 * Create a {@link Deferred} backed by a {@link Promise}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param <T>
+	 * 		type of the expected value
+	 *
+	 * @return a new {@link reactor.core.composable.Deferred}
+	 */
+	public static <T> Deferred<T, Promise<T>> defer(Environment env) {
+		return defer(env, env.getDefaultDispatcher());
+	}
+
+	/**
+	 * Create a {@link Deferred} backed by a {@link Promise}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param dispatcher
+	 * 		the name of the {@link reactor.event.dispatch.Dispatcher} to use
+	 * @param <T>
+	 * 		type of the expected value
+	 *
+	 * @return a new {@link reactor.core.composable.Deferred}
+	 */
+	public static <T> Deferred<T, Promise<T>> defer(Environment env, String dispatcher) {
+		return defer(env, env.getDispatcher(dispatcher));
+	}
+
+	/**
+	 * Create a {@link Deferred} backed by a {@link Promise}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param dispatcher
+	 * 		the {@link reactor.event.dispatch.Dispatcher} to use
+	 * @param <T>
+	 * 		type of the expected value
+	 *
+	 * @return a new {@link reactor.core.composable.Deferred}
+	 */
+	public static <T> Deferred<T, Promise<T>> defer(Environment env, Dispatcher dispatcher) {
+		return new DeferredPromiseSpec<T>().env(env).dispatcher(dispatcher).get();
+	}
+
+	/**
+	 * Create a {@link Deferred} backed by a {@link Promise}.
+	 *
+	 * @param <T>
+	 * 		type of the expected value
+	 *
+	 * @return A {@link DeferredPromiseSpec}.
+	 */
+	public static <T> DeferredPromiseSpec<T> defer() {
+		return new DeferredPromiseSpec<T>();
+	}
+
+	/**
+	 * Create a {@link Deferred} backed by a {@link Promise} and producing the value for the {@link Promise} using the
+	 * given supplier.
+	 *
+	 * @param supplier
+	 * 		{@link Supplier} that will produce the value
+	 * @param <T>
+	 * 		type of the expected value
+	 *
+	 * @return A {@link PromiseSpec}.
+	 */
+	public static <T> PromiseSpec<T> task(Supplier<T> supplier) {
+		return new PromiseSpec<T>().supply(supplier);
+	}
+
+	/**
+	 * Create a {@link Deferred} backed by a {@link Promise} and use the given value to complete the {@link Promise}
+	 * immediately.
+	 *
+	 * @param value
+	 * 		the value to complete the {@link Promise} with
+	 * @param <T>
+	 * 		the type of the value
+	 *
+	 * @return A {@link PromiseSpec} that will produce a {@link Promise} that is completed with the given value
+	 */
+	public static <T> PromiseSpec<T> success(T value) {
+		return new PromiseSpec<T>().success(value);
+	}
+
+	/**
+	 * Create a {@link Deferred} backed by a {@link Promise} and use the given error to complete the {@link Promise}
+	 * immediately.
+	 *
+	 * @param error
+	 * 		the error to complete the {@link Promise} with
+	 * @param <T>
+	 * 		the type of the value
+	 *
+	 * @return A {@link PromiseSpec} that will produce a {@link Promise} that is completed with the given error
+	 */
+	public static <T> PromiseSpec<T> error(Throwable error) {
+		return new PromiseSpec<T>().error(error);
+	}
+
+	/**
+	 * Merge given promises into a new a {@literal Promise} that will be fulfilled when all of the given {@literal Promise
+	 * Promises} have been fulfilled.
+	 *
+	 * @param promises
+	 * 		The promises to use.
+	 * @param <T>
+	 * 		The type of the function result.
+	 *
+	 * @return a {@link Promise}.
+	 */
+	public static <T> Promise<List<T>> when(Promise<T>... promises) {
+		return when(Arrays.asList(promises));
+	}
+
+	/**
+	 * Merge given deferred promises into a new a {@literal Promise} that will be fulfilled when all of the given
+	 * {@literal Deferred Deferreds} have been fulfilled.
+	 *
+	 * @param promises
+	 * 		The promises to use.
+	 * @param <T>
+	 * 		The type of the function result.
+	 *
+	 * @return a {@link Promise}.
+	 */
+	public static <T> Promise<List<T>> when(Deferred<T, Promise<T>>... promises) {
+		return when(deferredToPromises(promises));
+	}
+
+	/**
+	 * Aggregate given promises into a new a {@literal Promise} that will be fulfilled when all of the given {@literal
+	 * Promise Promises} have been fulfilled.
+	 *
+	 * @param promises
+	 * 		The promises to use.
+	 * @param <T>
+	 * 		The type of the function result.
+	 *
+	 * @return a {@link DeferredPromiseSpec}.
+	 */
+	public static <T> Promise<List<T>> when(Collection<? extends Promise<T>> promises) {
+		final AtomicInteger count = new AtomicInteger(promises.size());
+		final List<T> values = FastList.newList(promises.size());
+		final Deferred<List<T>, Promise<List<T>>> d = new DeferredPromiseSpec<List<T>>()
+				.synchronousDispatcher()
+				.get();
+
+		int i = 0;
+		for (Promise<T> promise : promises) {
+			final int idx = i++;
+			if (promise.isComplete()) {
+				count.decrementAndGet();
+				try {
+					values.add(idx, promise.get());
+				} catch (Throwable t) {
+					d.accept(t);
+					return d.compose();
+				}
+			} else {
+				promise
+						.onSuccess(new Consumer<T>() {
+							@Override
+							public void accept(T t) {
+								values.add(idx, t);
+								if (count.decrementAndGet() == 0) {
+									if (!d.compose().isComplete()) {
+										d.accept(values);
+									}
+								}
+							}
+						})
+						.onError(new Consumer<Throwable>() {
+							@Override
+							public void accept(Throwable throwable) {
+								count.decrementAndGet();
+								if (!d.compose().isComplete()) {
+									d.accept(throwable);
+								} else {
+									LoggerFactory.getLogger(Promises.class.getName() + ".when")
+									             .error(throwable.getMessage(), throwable);
+								}
+							}
+						});
+			}
+		}
+
+		return d.compose();
+	}
+
+
+	/**
+	 * Pick the first result coming from any of the given promises and populate a new {@literal Promise}.
+	 *
+	 * @param promises
+	 * 		The deferred promises to use.
+	 * @param <T>
+	 * 		The type of the function result.
+	 *
+	 * @return a {@link Promise}.
+	 */
+	public static <T> Promise<T> any(Deferred<T, Promise<T>>... promises) {
+		return any(deferredToPromises(promises));
+	}
+
+	/**
+	 * Pick the first result coming from any of the given promises and populate a new {@literal Promise}.
+	 *
+	 * @param promises
+	 * 		The deferred promises to use.
+	 * @param <T>
+	 * 		The type of the function result.
+	 *
+	 * @return a {@link Promise}.
+	 */
+	public static <T> Promise<T> any(Promise<T>... promises) {
+		return any(Arrays.asList(promises));
+	}
+
+
+	/**
+	 * Pick the first result coming from any of the given promises and populate a new {@literal Promise}.
+	 *
+	 * @param promises
+	 * 		The promises to use.
+	 * @param <T>
+	 * 		The type of the function result.
+	 *
+	 * @return a {@link DeferredStreamSpec}.
+	 */
+	public static <T> Promise<T> any(Collection<? extends Promise<T>> promises) {
+		Stream<T> deferredStream = new DeferredStreamSpec<T>()
+				.synchronousDispatcher()
+				.batchSize(promises.size())
+				.get()
+				.compose();
+
+		Stream<T> firstStream = deferredStream.first();
+
+		Promise<T> resultPromise = new DeferredPromiseSpec<T>()
+				.link(firstStream)
+				.get()
+				.compose();
+
+		firstStream.connectValues(resultPromise).connectErrors(resultPromise);
+
+		for (Promise<T> promise : promises) {
+			promise.connectErrors(deferredStream).connectValues(deferredStream);
+		}
+
+		return resultPromise;
+	}
+
+	/**
+	 * Consume the next value of the given {@link reactor.core.composable.Composable} and fulfill the returned {@link
+	 * reactor.core.composable.Promise} on the next value.
+	 *
+	 * @param composable
+	 * 		the {@literal Composable} to consume the next value from
+	 * @param <T>
+	 * 		type of the value
+	 *
+	 * @return a {@link reactor.core.composable.Promise} that will be fulfilled with the next value coming into the given
+	 * Composable
+	 */
+	public static <T> Promise<T> next(Composable<T> composable) {
+		final AtomicBoolean called = new AtomicBoolean(false);
+		final Deferred<T, Promise<T>> d = Promises.<T>defer().get();
+
+		composable
+				.when(Throwable.class, new Consumer<Throwable>() {
+					@Override
+					public void accept(Throwable throwable) {
+						if (!called.get()
+								&& called.compareAndSet(false, true)
+								&& !d.compose().isComplete()) {
+							d.accept(throwable);
+						}
+					}
+				})
+				.consume(new Consumer<T>() {
+					@Override
+					public void accept(T t) {
+						if (!called.get()
+								&& called.compareAndSet(false, true)
+								&& !d.compose().isComplete()) {
+							d.accept(t);
+						}
+					}
+				});
+
+		return d.compose();
+	}
+
+	private static <T> List<Promise<T>> deferredToPromises(Deferred<T, Promise<T>>... promises) {
+		List<Promise<T>> promiseList = new ArrayList<Promise<T>>();
+		for (Deferred<T, Promise<T>> deferred : promises) {
+			promiseList.add(deferred.compose());
+		}
+		return promiseList;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/spec/StreamSpec.java b/reactor-core/src/main/java/reactor/core/composable/spec/StreamSpec.java
new file mode 100644
index 0000000..30430c4
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/spec/StreamSpec.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.composable.spec;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+
+import reactor.core.composable.Composable;
+import reactor.core.composable.Stream;
+import reactor.event.selector.Selector;
+import reactor.function.Supplier;
+import reactor.tuple.Tuple2;
+
+/**
+ * A helper class for specifying a bounded {@link Stream}. {@link #each} must be called to
+ * provide the stream with its values.
+ *
+ * @param <T> The type of values that the stream contains.
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public final class StreamSpec<T> extends ComposableSpec<StreamSpec<T>, Stream<T>> {
+
+	private int batchSize = -1;
+	private Iterable<T> values;
+	private Supplier<T> valuesSupplier;
+
+	/**
+	 * Configures the stream to have the given {@code batchSize}. A value of {@code -1}, which
+	 * is the default configuration, configures the stream to not be batched.
+	 *
+	 * @param batchSize The batch size of the stream
+	 * @return {@code this}
+	 */
+	public StreamSpec<T> batchSize(int batchSize) {
+		this.batchSize = batchSize;
+		return this;
+	}
+
+	/**
+	 * Configures the stream to contain the given {@code values}.
+	 *
+	 * @param values The stream's values
+	 * @return {@code this}
+	 */
+	public StreamSpec<T> each(Iterable<T> values) {
+		this.values = values;
+		return this;
+	}
+
+
+	/**
+	 * Configures the stream to pass value from a {@link Supplier} on flush.
+	 *
+	 * @param supplier The stream's value generator
+	 * @return {@code this}
+	 */
+	public StreamSpec<T> generate(Supplier<T> supplier) {
+		this.valuesSupplier = supplier;
+		return this;
+	}
+
+	@Override
+	protected Stream<T> createComposable(Environment env, Observable observable,
+	                                     Tuple2<Selector, Object> accept) {
+
+		if (accept == null && values == null &&  valuesSupplier == null) {
+			throw new IllegalStateException("A bounded stream must be configured with some values source. Use " +
+					DeferredStreamSpec.class.getSimpleName() + " to create a stream with no initial values or supplier");
+		}
+
+		Stream<T> stream = new Stream<T>(observable, batchSize, null, accept, env);
+		if(accept != null){
+			return stream;
+		}
+		else if(values == null){
+			return stream.propagate(valuesSupplier);
+		}else{
+			return stream.propagate(values);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/spec/Streams.java b/reactor-core/src/main/java/reactor/core/composable/spec/Streams.java
new file mode 100644
index 0000000..2bfa76a
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/spec/Streams.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.composable.spec;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Stream;
+import reactor.event.dispatch.Dispatcher;
+import reactor.event.dispatch.SynchronousDispatcher;
+import reactor.event.selector.Selector;
+import reactor.function.Supplier;
+import reactor.tuple.Tuple2;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * A public factory to build {@link Stream Streams} that use a {@link SynchronousDispatcher}.
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ */
+public abstract class Streams {
+
+	/**
+	 * Build a deferred {@literal Stream}, ready to accept values.
+	 *
+	 * @param env
+	 * 		the Reactor {@link reactor.core.Environment} to use
+	 * @param <T>
+	 * 		the type of values passing through the {@literal Stream}
+	 *
+	 * @return a new {@link reactor.core.composable.Deferred}
+	 */
+	public static <T> Deferred<T, Stream<T>> defer(Environment env) {
+		return defer(env, env.getDefaultDispatcher());
+	}
+
+	/**
+	 * Build a deferred {@literal Stream}, ready to accept values.
+	 *
+	 * @param env
+	 * 		the Reactor {@link reactor.core.Environment} to use
+	 * @param dispatcher
+	 * 		the name of the {@link reactor.event.dispatch.Dispatcher} to use
+	 * @param <T>
+	 * 		the type of values passing through the {@literal Stream}
+	 *
+	 * @return a new {@link reactor.core.composable.Deferred}
+	 */
+	public static <T> Deferred<T, Stream<T>> defer(Environment env, String dispatcher) {
+		return defer(env, env.getDispatcher(dispatcher));
+	}
+
+	/**
+	 * Build a deferred {@literal Stream}, ready to accept values.
+	 *
+	 * @param env
+	 * 		the Reactor {@link reactor.core.Environment} to use
+	 * @param dispatcher
+	 * 		the {@link reactor.event.dispatch.Dispatcher} to use
+	 * @param <T>
+	 * 		the type of values passing through the {@literal Stream}
+	 *
+	 * @return a new {@link reactor.core.composable.Deferred}
+	 */
+	public static <T> Deferred<T, Stream<T>> defer(Environment env, Dispatcher dispatcher) {
+		return new DeferredStreamSpec<T>().env(env).dispatcher(dispatcher).get();
+	}
+
+	/**
+	 * Build a deferred {@literal Stream}, ready to accept values.
+	 *
+	 * @param <T>
+	 * 		the type of values passing through the {@literal Stream}
+	 *
+	 * @return a new {@link DeferredStreamSpec}
+	 */
+	public static <T> DeferredStreamSpec<T> defer() {
+		return new DeferredStreamSpec<T>();
+	}
+
+
+	/**
+	 * Attach a Stream to the {@link Observable} with the specified {@link Selector} and key.
+	 *
+	 * @param observable
+	 * 		the {@link Observable} to observe
+	 * @param acceptSelector
+	 * 		the {@link Selector}/{@literal Object} tuple to listen to
+	 * @param key
+	 * 		the key to publish to
+	 * @param <T>
+	 * 		the type of values passing through the {@literal Stream}
+	 *
+	 * @return a new {@link DeferredStreamSpec}
+	 * @since 1.1
+	 */
+	public static <T> Stream<T> on(Observable observable, Selector acceptSelector, Object key) {
+		return new StreamSpec<T>().observable(observable).acceptSelector(Tuple2.of(acceptSelector,key)).get();
+	}
+
+	/**
+	 * Attach a Stream to the {@link Observable} with the specified {@link Selector}.
+	 *
+	 * @param observable
+	 * 		the {@link Observable} to observe
+	 * @param acceptSelector
+	 * 		the {@link Selector}/{@literal Object} tuple to listen/publish to
+	 * @param <T>
+	 * 		the type of values passing through the {@literal Stream}
+	 *
+	 * @return a new {@link DeferredStreamSpec}
+	 * @since 1.1
+	 */
+	public static <T> Stream<T> on(Observable observable, Selector acceptSelector) {
+		return on(observable, acceptSelector, acceptSelector.getObject());
+	}
+
+	/**
+	 * Build a deferred {@literal Stream} that will implicitly {@link Deferred#accept(Object)}
+	 * the given value whenever the {@link reactor.core.composable.Stream#flush()} function
+	 * is invoked.
+	 *
+	 * @param value
+	 * 		The value to {@code accept()}
+	 * @param <T>
+	 * 		type of the value
+	 *
+	 * @return a {@link DeferredStreamSpec} based on the given value
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> StreamSpec<T> defer(T value) {
+		return  new StreamSpec<T>().each(Arrays.asList(value)).batchSize(1);
+	}
+
+	/**
+	 * Build a deferred {@literal Stream} that will implicitly {@link Deferred#accept(Object)}
+	 * the supplied value whenever the {@link reactor.core.composable.Stream#flush()} function
+	 * is invoked.
+	 *
+	 * @param value
+	 * 		The value to {@code accept()}
+	 * @param <T>
+	 * 		type of the value
+	 *
+	 * @return a {@link DeferredStreamSpec} based on the given value
+	 * @since 1.1
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> StreamSpec<T> defer(Supplier<T> value) {
+		return  new StreamSpec<T>().generate(value).batchSize(1);
+	}
+
+	/**
+	 * Build a deferred {@literal Stream} that will implicitly {@link Deferred#accept(Object)}
+	 * the given values whenever the {@link reactor.core.composable.Stream#flush()} function
+	 * is invoked. If the {@code values} are a {@code Collection} the Stream's batch size will
+	 * be set to the Collection's {@link Collection#size()}.
+	 *
+	 * @param values
+	 * 		The values to {@code accept()}
+	 * @param <T>
+	 * 		type of the values
+	 *
+	 * @return a {@link StreamSpec} based on the given values
+	 */
+	public static <T> StreamSpec<T> defer(Iterable<T> values) {
+		int batchSize = (values instanceof Collection ? ((Collection<?>)values).size() : -1);
+		return new StreamSpec<T>().each(values).batchSize(batchSize);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/composable/spec/package-info.java b/reactor-core/src/main/java/reactor/core/composable/spec/package-info.java
new file mode 100644
index 0000000..2d6c6a6
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/composable/spec/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Specs help create {@link reactor.core.composable.Composable Composables} by providing a fluent API to specify
+ * common options.
+ */
+package reactor.core.composable.spec;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/configuration/ConfigurationReader.java b/reactor-core/src/main/java/reactor/core/configuration/ConfigurationReader.java
new file mode 100644
index 0000000..fdee91d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/configuration/ConfigurationReader.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.configuration;
+
+
+/**
+ * A {@code ConfigurationReader} is used to read Reactor configuration.
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public interface ConfigurationReader {
+
+	/**
+	 * Reads the configuration
+	 *
+	 * @return The configuration. Never {@code null}.
+	 */
+	ReactorConfiguration read();
+}
diff --git a/reactor-core/src/main/java/reactor/core/configuration/DispatcherConfiguration.java b/reactor-core/src/main/java/reactor/core/configuration/DispatcherConfiguration.java
new file mode 100644
index 0000000..ccab581
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/configuration/DispatcherConfiguration.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.configuration;
+
+import reactor.core.dynamic.annotation.Dispatcher;
+
+/**
+ * An encapsulation of the configuration for a {@link Dispatcher}.
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public final class DispatcherConfiguration {
+
+	private final String name;
+
+	private final DispatcherType type;
+
+	private final Integer backlog;
+
+	private final Integer size;
+
+	public DispatcherConfiguration(String name, DispatcherType type, Integer backlog, Integer size) {
+		this.name = name;
+		this.type = type;
+		this.backlog = backlog;
+		this.size = size;
+	}
+
+	/**
+	 * Returns the configured size, or {@code null} if the size was not configured
+	 *
+	 * @return The size
+	 */
+	public Integer getSize() {
+		return size;
+	}
+
+	/**
+	 * Returns the configured backlog, or {@code null} if the backlog was not configured
+	 *
+	 * @return The backlog
+	 */
+	public Integer getBacklog() {
+		return backlog;
+	}
+
+	/**
+	 * Returns the name of the Dispatcher. Never {@code null}.
+	 *
+	 * @return The name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * Returns the type of the Dispatcher. Never {@code null}.
+	 *
+	 * @return The type
+	 */
+	public DispatcherType getType() {
+		return type;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/configuration/DispatcherType.java b/reactor-core/src/main/java/reactor/core/configuration/DispatcherType.java
new file mode 100644
index 0000000..5fbcff2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/configuration/DispatcherType.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.configuration;
+
+import com.lmax.disruptor.RingBuffer;
+import reactor.core.dynamic.annotation.Dispatcher;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * An enumeration of supported types of {@link Dispatcher}.
+ *
+ * @author Andy Wilkinson
+ */
+public enum DispatcherType {
+
+	/**
+	 * A {@link Dispatcher} which uses an event loop for dispatching
+	 */
+	EVENT_LOOP,
+
+	/**
+	 * A {@link Dispatcher} which uses a {@link RingBuffer} for dispatching
+	 */
+	RING_BUFFER,
+
+	/**
+	 * A {@link Dispatcher} which uses the current thread for dispatching
+	 */
+	SYNCHRONOUS,
+
+	/**
+	 * A {@link Dispatcher} which uses a {@link ThreadPoolExecutor} for dispatching
+	 */
+	THREAD_POOL_EXECUTOR,
+
+	/**
+	 * A {@link Dispatcher} which uses a multi-threaded {@literal RingBuffer} for dispatching
+	 */
+	WORK_QUEUE
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/configuration/PropertiesConfigurationReader.java b/reactor-core/src/main/java/reactor/core/configuration/PropertiesConfigurationReader.java
new file mode 100644
index 0000000..c563876
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/configuration/PropertiesConfigurationReader.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.configuration;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.util.IoUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link ConfigurationReader} that reads the configuration from properties files
+ * and System properties.
+ *
+ * @author Andy Wilkinson
+ */
+public class PropertiesConfigurationReader implements ConfigurationReader {
+
+	private static final Pattern REACTOR_NAME_PATTERN = Pattern.compile("reactor\\.dispatchers\\.(.+?)\\.type");
+
+	private static final String FORMAT_DISPATCHER_BACKLOG = "reactor.dispatchers.%s.backlog";
+	private static final String FORMAT_DISPATCHER_SIZE    = "reactor.dispatchers.%s.size";
+	private static final String FORMAT_DISPATCHER_TYPE    = "reactor.dispatchers.%s.type";
+	private static final String FORMAT_RESOURCE_NAME      = "/META-INF/reactor/%s.properties";
+
+	private static final String PROPERTY_PREFIX_REACTOR = "reactor.";
+
+	private static final String PROPERTY_NAME_PROFILES_ACTIVE    = "reactor.profiles.active";
+	private static final String PROPERTY_NAME_PROFILES_DEFAULT   = "reactor.profiles.default";
+	private static final String PROPERTY_NAME_DEFAULT_DISPATCHER = "reactor.dispatchers.default";
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	private final String defaultProfileNameDefault;
+
+	/**
+	 * Creates a new {@code PropertiesConfigurationReader} that, by default, will load its
+	 * configuration from {@code META-INF/reactor/default.properties}.
+	 */
+	public PropertiesConfigurationReader() {
+		this("default");
+	}
+
+	public PropertiesConfigurationReader(String defaultProfileNameDefault) {
+		this.defaultProfileNameDefault = defaultProfileNameDefault;
+	}
+
+	@Override
+	public ReactorConfiguration read() {
+		Properties configuration = new Properties();
+
+		applyProfile(loadDefaultProfile(), configuration);
+
+		for(Properties activeProfile : loadActiveProfiles()) {
+			applyProfile(activeProfile, configuration);
+		}
+
+		applySystemProperties(configuration);
+
+		String defaultDispatcherName = configuration.getProperty(PROPERTY_NAME_DEFAULT_DISPATCHER);
+
+		List<DispatcherConfiguration> dispatcherConfiguration = createDispatcherConfiguration(configuration);
+
+		return new ReactorConfiguration(dispatcherConfiguration, defaultDispatcherName, configuration);
+	}
+
+	private Properties loadDefaultProfile() {
+		String defaultProfileName = System.getProperty(PROPERTY_NAME_PROFILES_DEFAULT, defaultProfileNameDefault);
+		Properties defaultProfile = loadProfile(defaultProfileName);
+		return defaultProfile;
+	}
+
+	private List<Properties> loadActiveProfiles() {
+		List<Properties> activeProfiles = new ArrayList<Properties>();
+		if(null != System.getProperty(PROPERTY_NAME_PROFILES_ACTIVE)) {
+			String[] profileNames = System.getProperty(PROPERTY_NAME_PROFILES_ACTIVE).split(",");
+			for(String profileName : profileNames) {
+				activeProfiles.add(loadProfile(profileName.trim()));
+			}
+		}
+		return activeProfiles;
+	}
+
+	private void applyProfile(Properties profile, Properties configuration) {
+		configuration.putAll(profile);
+	}
+
+	private void applySystemProperties(Properties configuration) {
+		for(String prop : System.getProperties().stringPropertyNames()) {
+			if(prop.startsWith(PROPERTY_PREFIX_REACTOR)) {
+				configuration.put(prop, System.getProperty(prop));
+			}
+		}
+	}
+
+	private List<DispatcherConfiguration> createDispatcherConfiguration(Properties configuration) {
+		List<String> dispatcherNames = getDispatcherNames(configuration);
+		List<DispatcherConfiguration> dispatcherConfigurations = new ArrayList<DispatcherConfiguration>(dispatcherNames
+				                                                                                                .size());
+		for(String dispatcherName : dispatcherNames) {
+			DispatcherType type = getType(dispatcherName, configuration);
+			if(type != null) {
+				dispatcherConfigurations.add(new DispatcherConfiguration(dispatcherName,
+				                                                         type,
+				                                                         getBacklog(dispatcherName,
+				                                                                    configuration),
+				                                                         getSize(dispatcherName, configuration)));
+			}
+		}
+		return dispatcherConfigurations;
+	}
+
+	private List<String> getDispatcherNames(Properties configuration) {
+		List<String> dispatcherNames = new ArrayList<String>();
+
+		for(Object propertyName : configuration.keySet()) {
+			Matcher matcher = REACTOR_NAME_PATTERN.matcher((String)propertyName);
+			if(matcher.matches()) {
+				dispatcherNames.add(matcher.group(1));
+			}
+		}
+
+		return dispatcherNames;
+	}
+
+	private DispatcherType getType(String dispatcherName, Properties configuration) {
+		String type = configuration.getProperty(String.format(FORMAT_DISPATCHER_TYPE, dispatcherName));
+		if("eventLoop".equals(type)) {
+			return DispatcherType.EVENT_LOOP;
+		} else if("ringBuffer".equals(type)) {
+			return DispatcherType.RING_BUFFER;
+		} else if("synchronous".equals(type)) {
+			return DispatcherType.SYNCHRONOUS;
+		} else if("threadPoolExecutor".equals(type)) {
+			return DispatcherType.THREAD_POOL_EXECUTOR;
+		} else if("workQueue".equals(type)) {
+			return DispatcherType.WORK_QUEUE;
+		} else {
+			logger.warn("The type '{}' of Dispatcher '{}' is not recognized", type, dispatcherName);
+			return null;
+		}
+	}
+
+	private Integer getBacklog(String dispatcherName, Properties configuration) {
+		return getInteger(String.format(FORMAT_DISPATCHER_BACKLOG, dispatcherName), configuration);
+	}
+
+	private Integer getSize(String dispatcherName, Properties configuration) {
+		return getInteger(String.format(FORMAT_DISPATCHER_SIZE, dispatcherName), configuration);
+	}
+
+	private Integer getInteger(String propertyName, Properties configuration) {
+		String property = configuration.getProperty(propertyName);
+		if(property != null) {
+			return Integer.parseInt(property);
+		} else {
+			return null;
+		}
+	}
+
+	protected Properties loadProfile(String name) {
+		Properties properties = new Properties();
+		InputStream inputStream = getClass().getResourceAsStream(String.format(FORMAT_RESOURCE_NAME, name));
+		if(null != inputStream) {
+			try {
+				properties.load(inputStream);
+			} catch(IOException e) {
+				logger.error("Failed to load properties from '{}' for profile '{}'",
+				             String.format(FORMAT_RESOURCE_NAME, name),
+				             name,
+				             e);
+			} finally {
+				IoUtils.closeQuietly(inputStream);
+			}
+		} else {
+			logger.debug("No properties file found in the classpath at '{}' for profile '{}'", String.format(
+					FORMAT_RESOURCE_NAME,
+					name), name);
+		}
+		return properties;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/configuration/ReactorConfiguration.java b/reactor-core/src/main/java/reactor/core/configuration/ReactorConfiguration.java
new file mode 100644
index 0000000..0bf58b2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/configuration/ReactorConfiguration.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.configuration;
+
+import java.util.List;
+import java.util.Properties;
+
+import reactor.util.Assert;
+
+/**
+ * An encapsulation of configuration for Reactor
+ *
+ * @author Andy Wilkinson
+ * @author Jon Brisbin
+ */
+public class ReactorConfiguration {
+
+	private final List<DispatcherConfiguration> dispatcherConfigurations;
+
+	private final String defaultDispatcherName;
+
+	private final Properties properties;
+
+	public ReactorConfiguration(List<DispatcherConfiguration> dispatcherConfigurations, String defaultDispatcherName, Properties properties) {
+		Assert.notNull(dispatcherConfigurations, "'dispatcherConfigurations' must not be null");
+		Assert.notNull(defaultDispatcherName, "'defaultDispatcherName' must not be null");
+		Assert.notNull(properties, "'properties' must not be null");
+
+		this.dispatcherConfigurations = dispatcherConfigurations;
+		this.defaultDispatcherName = defaultDispatcherName;
+		this.properties = properties;
+	}
+
+	/**
+	 * Returns a {@link List} of {@link DispatcherConfiguration DispatcherConfigurations}. If no
+	 * dispatchers are configured, an empty list is returned. Never returns {@code null}.
+	 *
+	 * @return The dispatcher configurations
+	 */
+	public List<DispatcherConfiguration> getDispatcherConfigurations() {
+		return dispatcherConfigurations;
+	}
+
+	/**
+	 * Returns the name of the default dispatcher. Never {@code null}.
+	 *
+	 * @return The default dispatcher's name
+	 */
+	public String getDefaultDispatcherName() {
+		return defaultDispatcherName;
+	}
+
+	/**
+	 * Additional configuration properties. Never {@code null}.
+	 *
+	 * @return The additional configuration properties.
+	 */
+	public Properties getAdditionalProperties() {
+		return properties;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/configuration/package-info.java b/reactor-core/src/main/java/reactor/core/configuration/package-info.java
new file mode 100644
index 0000000..6e28fb0
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/configuration/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Components to facilitate flexible configuration of the Reactor {@link reactor.core.Environment}.
+ */
+package reactor.core.configuration;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/DynamicReactor.java b/reactor-core/src/main/java/reactor/core/dynamic/DynamicReactor.java
new file mode 100644
index 0000000..9bad7f6
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/DynamicReactor.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic;
+
+/**
+ * A {@literal DynamicReactor} is an arbitrary interface that a proxy generator can use to wire calls to the interface
+ * to appropriate {@link reactor.core.Reactor#on(reactor.event.selector.Selector, reactor.function.Consumer)} and {@link
+ * reactor.core.Reactor#notify(Object, reactor.event.Event)} calls.
+ *
+ * @author Jon Brisbin
+ */
+public interface DynamicReactor {
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/DynamicReactorFactory.java b/reactor-core/src/main/java/reactor/core/dynamic/DynamicReactorFactory.java
new file mode 100644
index 0000000..1e59a65
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/DynamicReactorFactory.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import reactor.convert.Converter;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.dynamic.annotation.Dispatcher;
+import reactor.core.dynamic.annotation.Notify;
+import reactor.core.dynamic.annotation.On;
+import reactor.core.dynamic.reflect.MethodNotificationKeyResolver;
+import reactor.core.dynamic.reflect.MethodSelectorResolver;
+import reactor.core.dynamic.reflect.SimpleMethodNotificationKeyResolver;
+import reactor.core.dynamic.reflect.SimpleMethodSelectorResolver;
+import reactor.core.spec.ReactorSpec;
+import reactor.core.spec.Reactors;
+import reactor.event.Event;
+import reactor.event.dispatch.SynchronousDispatcher;
+import reactor.event.registry.Registration;
+import reactor.event.routing.ArgumentConvertingConsumerInvoker;
+import reactor.event.routing.ConsumerInvoker;
+import reactor.event.selector.Selector;
+import reactor.function.Consumer;
+import reactor.function.Function;
+
+/**
+ * A {@literal DynamicReactorFactory} is responsible for generating a {@link Proxy} based on the given interface, that
+ * intercepts calls to the interface and translates them into the appropriate {@link
+ * Reactor#on(reactor.event.selector.Selector, reactor.function.Consumer)} or {@link Reactor#notify(Object, Event)}
+ * calls. Methods that are translated to {@code on} calls should take a single argument that is a {@link Consumer},
+ * {@link Function}, {@link Runnable}, or {@link Callable}. Methods that are translated to {@code notify} calls should
+ * take zero or one arguments. If the method takes zero arguments it will be translated to {@link
+ * Reactor#notify(Object)}. IF the method takes one argument it will be translated to {@link Reactor#notify(Object,
+ * Event)}, {@link Event#wrap(Object) wrapping} the argument in an {@link Event} if necessary.
+ * <p/>
+ * The translation of calls on the interface to calls to {@code on} and the selector that is used is determined by the
+ * {@link MethodSelectorResolver}s. The translation of calls on the interface to calls to {@link Reactor#notify(Object,
+ * Event) notify} is determined by the {@link MethodNotificationKeyResolver}s.
+ * <p/>
+ * By default, the creation of the selector for {@code on} calls will look for the {@link On} annotation. In its
+ * absence, if the method name begins with {@code on}, the method name minus the on prefix and with the first character
+ * lower-cased, will be used to create the selector. For example, the selector for a method named {@code onAlpha} will
+ * be {@code "alpha"}.
+ * <p/>
+ * By default, the translation for {@code notify} calls will look for the {@link Notify} annotation. In its absence, if
+ * the method name begins with {@code notify}, the method name minus the notify prefix and with the first character
+ * lower-cased, will be used to create the notification key. For example, the notification key for a method named
+ * {@code notifyAlpha} will be {@code "alpha"}.
+ *
+ * @param <T>
+ * 		The type to proxy
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+public class DynamicReactorFactory<T extends DynamicReactor> {
+
+	private final Environment                         env;
+	private final Class<T>                            type;
+	private final List<MethodSelectorResolver>        selectorResolvers;
+	private final List<MethodNotificationKeyResolver> notificationKeyResolvers;
+	private final ConsumerInvoker                     consumerInvoker;
+	private final Converter                           converter;
+	private final Map<Method, DynamicMethod> dynamicMethods = new HashMap<Method, DynamicMethod>();
+
+
+	/**
+	 * Creates a new DynamicReactorFactory that will use the given {@code env} when creating its {@link Reactor}. The
+	 * proxy
+	 * that is generated will be based upon the given {@code type}. A {@link SimpleMethodSelectorResolver} will be used to
+	 * create selectors for proxied methods. A {@link SimpleMethodNotificationKeyResolver} will be used to create
+	 * notification keys for proxied methods.
+	 *
+	 * @param env
+	 * 		The Environment to use when creating the underlying Reactor.
+	 * @param type
+	 * 		The type of the proxy
+	 */
+	public DynamicReactorFactory(Environment env,
+	                             Class<T> type) {
+		this(env,
+		     type,
+		     Arrays.<MethodSelectorResolver>asList(new SimpleMethodSelectorResolver()),
+		     Arrays.<MethodNotificationKeyResolver>asList(new SimpleMethodNotificationKeyResolver()),
+		     new ArgumentConvertingConsumerInvoker(null),
+		     null);
+	}
+
+	/**
+	 * Creates a new DynamicReactorFactory that will use the given {@code env} when creating its {@link Reactor}. The
+	 * proxy
+	 * that is generated will be based upon the given {@code type}. The {@code selectorResolvers} will be used to create
+	 * selectors for proxied methods and the {@code notificationKeyResolvers} will be used to create notification keys for
+	 * proxied methods.
+	 *
+	 * @param env
+	 * 		The Environment to use when creating the underlying Reactor.
+	 * @param type
+	 * 		The type of the proxy
+	 * @param selectorResolvers
+	 * 		used to resolve the selectors for proxied methods
+	 * @param notificationKeyResolvers
+	 * 		used to resolve the notification keys for proxied methods
+	 */
+	public DynamicReactorFactory(Environment env,
+	                             Class<T> type,
+	                             List<MethodSelectorResolver> selectorResolvers,
+	                             List<MethodNotificationKeyResolver> notificationKeyResolvers,
+	                             ConsumerInvoker consumerInvoker,
+	                             Converter converter) {
+		this.env = env;
+		this.type = type;
+		this.selectorResolvers = selectorResolvers;
+		this.notificationKeyResolvers = notificationKeyResolvers;
+		this.consumerInvoker = consumerInvoker;
+		this.converter = converter;
+	}
+
+	/**
+	 * Get the list of {@link MethodSelectorResolver}s in use.
+	 *
+	 * @return The {@link MethodSelectorResolver}s in use.
+	 */
+	public List<MethodSelectorResolver> getSelectorResolvers() {
+		return Collections.unmodifiableList(selectorResolvers);
+	}
+
+	/**
+	 * Get the {@link ConsumerInvoker} to use when invoking {@link Consumer Consumers}.
+	 *
+	 * @return The {@link ConsumerInvoker} in use.
+	 */
+	public ConsumerInvoker getConsumerInvoker() {
+		return consumerInvoker;
+	}
+
+	/**
+	 * Get the {@link Converter} to use when coercing arguments.
+	 *
+	 * @return The {@link Converter} to use.
+	 */
+	public Converter getConverter() {
+		return converter;
+	}
+
+	/**
+	 * Generate a {@link Proxy} based on the type used to create this factory.
+	 *
+	 * @return A proxy based on the type used to create the factory
+	 */
+	@SuppressWarnings("unchecked")
+	public T create() {
+		return (T)Proxy.newProxyInstance(
+				DynamicReactorFactory.class.getClassLoader(),
+				new Class[]{type},
+				new ReactorInvocationHandler<T>(type)
+		);
+	}
+
+	private class ReactorInvocationHandler<U> implements InvocationHandler {
+		private final Map<Method, Selector> selectors        = new HashMap<Method, Selector>();
+		private final Map<Method, Object>   notificationKeys = new HashMap<Method, Object>();
+
+		private final Reactor reactor;
+
+		private ReactorInvocationHandler(Class<U> type) {
+			Dispatcher d = find(type, Dispatcher.class);
+			this.reactor = createReactor(d);
+
+			for(Method m : type.getDeclaredMethods()) {
+				if(m.getDeclaringClass() == Object.class || m.getName().contains("$")) {
+					continue;
+				}
+
+				DynamicMethod dm = new DynamicMethod();
+
+				dm.returnsRegistration = Registration.class.isAssignableFrom(m.getReturnType());
+				dm.returnsProxy = type.isAssignableFrom(m.getReturnType());
+
+				if(isOn(m)) {
+					for(MethodSelectorResolver msr : selectorResolvers) {
+						if(msr.supports(m)) {
+							Selector sel = msr.apply(m);
+							if(null != sel) {
+								selectors.put(m, sel);
+								dynamicMethods.put(m, dm);
+								break;
+							}
+						}
+					}
+				} else if(isNotify(m)) {
+					for(MethodNotificationKeyResolver notificationKeyResolver : notificationKeyResolvers) {
+						if(notificationKeyResolver.supports(m)) {
+							String notificationKey = notificationKeyResolver.apply(m);
+							if(null != notificationKey) {
+								notificationKeys.put(m, notificationKey);
+								dynamicMethods.put(m, dm);
+								break;
+							}
+						}
+					}
+				}
+			}
+		}
+
+		@Override
+		@SuppressWarnings({"unchecked", "rawtypes"})
+		public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable {
+			final DynamicMethod dm = dynamicMethods.get(method);
+
+			if(isOn(method)) {
+				Selector sel = selectors.get(method);
+				if(null == sel) {
+					return proxy;
+				}
+
+				if(args.length > 1) {
+					throw new IllegalArgumentException("Only pass a single Consumer, Function, Runnable, or Callable");
+				}
+
+				final Object arg = args[0];
+
+				Registration reg = null;
+				if(Consumer.class.isInstance(arg)) {
+					reg = reactor.on(sel, (Consumer)arg);
+				} else if(Function.class.isInstance(arg)) {
+					reg = reactor.receive(sel, (Function)arg);
+				} else if(Runnable.class.isInstance(arg)) {
+					reg = reactor.on(sel, new Consumer<Event<?>>() {
+						@Override
+						public void accept(Event<?> event) {
+							((Runnable)arg).run();
+						}
+					});
+				} else if(Callable.class.isInstance(arg)) {
+					reg = reactor.receive(sel, new Function<Event<?>, Object>() {
+						@Override
+						public Object apply(Event<?> event) {
+							try {
+								return ((Callable)arg).call();
+							} catch(Exception e) {
+								reactor.notify(e.getClass(), Event.wrap(e));
+							}
+							return null;
+						}
+					});
+				} else if(null == converter || !converter.canConvert(arg.getClass(), Consumer.class)) {
+					throw new IllegalArgumentException(
+							String.format("No Converter available to convert '%s' to Consumer", arg.getClass().getName())
+					);
+				}
+
+				return (dm.returnsRegistration ? reg : dm.returnsProxy ? proxy : null);
+			} else if(isNotify(method)) {
+				Object key = notificationKeys.get(method);
+				if(null == key) {
+					return proxy;
+				}
+
+				if(args.length == 0) {
+					reactor.notify(key);
+				} else if(args.length == 1) {
+					reactor.notify(key, (Event.class.isInstance(args[0]) ? (Event)args[0] : Event.wrap(args[0])));
+				} else {
+					// TODO: handle multiple args
+				}
+
+				return (dm.returnsProxy ? proxy : null);
+			} else {
+				throw new NoSuchMethodError(method.getName());
+			}
+		}
+
+		private Reactor createReactor(Dispatcher dispatcherAnnotation) {
+			ReactorSpec reactorSpec = Reactors.reactor().env(env);
+			if(dispatcherAnnotation != null) {
+				if("sync".equals(dispatcherAnnotation.value())) {
+					reactorSpec.dispatcher(new SynchronousDispatcher());
+				} else {
+					reactorSpec.dispatcher(dispatcherAnnotation.value());
+				}
+			}
+			return reactorSpec.get();
+		}
+	}
+
+	private static boolean isOn(Method m) {
+		return m.getName().startsWith("on") || null != m.getAnnotation(On.class);
+	}
+
+	private static boolean isNotify(Method m) {
+		return m.getName().startsWith("notify") || null != m.getAnnotation(Notify.class);
+	}
+
+	@SuppressWarnings("unchecked")
+	private static <T extends Annotation> T find(Class<?> type, Class<T> annoType) {
+		if(type.getDeclaredAnnotations().length > 0) {
+			for(Annotation anno : type.getDeclaredAnnotations()) {
+				if(annoType.isAssignableFrom(anno.getClass())) {
+					return ((T)anno);
+				}
+			}
+		}
+		return null;
+	}
+
+	private static final class DynamicMethod {
+		boolean returnsRegistration = false;
+		boolean returnsProxy        = true;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/annotation/Dispatcher.java b/reactor-core/src/main/java/reactor/core/dynamic/annotation/Dispatcher.java
new file mode 100644
index 0000000..3b03d5a
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/annotation/Dispatcher.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.dynamic.DynamicReactorFactory;
+
+/**
+ * Used on a {@class DynamicReactor} to specify the {@link Dispatcher} that should be used
+ * by the underlying {@link Reactor}. The {@code Dispatcher} is looked up in the
+ * {@link Environment} of the {@link DynamicReactorFactory}.
+ *
+ * @author Jon Brisbin
+ *
+ */
+ at Target(ElementType.TYPE)
+ at Retention(RetentionPolicy.RUNTIME)
+ at Inherited
+public @interface Dispatcher {
+
+	/**
+	 * The name of the dispatcher. The actual dispatcher is found by using the name to {@link
+	 * Environment#getDispatcher(String) look it up} in the {@link Environment}.
+	 *
+	 * @return The name of the dispatcher
+	 */
+	String value();
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/annotation/Notify.java b/reactor-core/src/main/java/reactor/core/dynamic/annotation/Notify.java
new file mode 100644
index 0000000..aa2814f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/annotation/Notify.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import reactor.core.Reactor;
+
+/**
+ * Annotation to denote that a method should proxy a call to an underlying {@link
+ * Reactor#notify(Object, reactor.event.Event)} or {@link Reactor#notify(Object)} call.
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ */
+ at Target(ElementType.METHOD)
+ at Retention(RetentionPolicy.RUNTIME)
+ at Inherited
+public @interface Notify {
+
+	/**
+	 * The string to use as the notification key
+	 *
+	 * @return the notification key
+	 */
+	String value() default "";
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/annotation/On.java b/reactor-core/src/main/java/reactor/core/dynamic/annotation/On.java
new file mode 100644
index 0000000..6def25b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/annotation/On.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import reactor.event.selector.ObjectSelector;
+import reactor.event.selector.Selector;
+import reactor.function.Consumer;
+
+/**
+ * Annotation to denote that a method should proxy a call to
+ * {@link reactor.core.Reactor#on(Selector, Consumer)}.
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ */
+ at Target(ElementType.METHOD)
+ at Retention(RetentionPolicy.RUNTIME)
+ at Inherited
+public @interface On {
+
+	/**
+	 * The string to use to create the {@link Selector}.
+	 *
+	 * @return the selector string
+	 *
+	 * @see ObjectSelector
+	 */
+	String value() default "";
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/annotation/package-info.java b/reactor-core/src/main/java/reactor/core/dynamic/annotation/package-info.java
new file mode 100644
index 0000000..022b438
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/annotation/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Annotations to influence how proxies listen for and publish events on a {@link reactor.core.Reactor}.
+ */
+package reactor.core.dynamic.annotation;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/package-info.java b/reactor-core/src/main/java/reactor/core/dynamic/package-info.java
new file mode 100644
index 0000000..29b1825
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * {@link reactor.core.dynamic.DynamicReactor DynamicReactors} generate proxies based on user-defined interfaces and
+ * do the dirty work of wiring event handlers and makes event publishing code more succinct.
+ */
+package reactor.core.dynamic;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/reflect/MethodNotificationKeyResolver.java b/reactor-core/src/main/java/reactor/core/dynamic/reflect/MethodNotificationKeyResolver.java
new file mode 100644
index 0000000..3690a0f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/reflect/MethodNotificationKeyResolver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.reflect;
+
+import java.lang.reflect.Method;
+
+import reactor.function.Function;
+import reactor.support.Supports;
+
+/**
+ * When given a {@link Method}, a {@code MethodNotificationKeyResolver} will attempt to return
+ * a notification key for the method.
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public interface MethodNotificationKeyResolver extends Supports<Method>, Function<Method, String> {
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/reflect/MethodSelectorResolver.java b/reactor-core/src/main/java/reactor/core/dynamic/reflect/MethodSelectorResolver.java
new file mode 100644
index 0000000..dd0b779
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/reflect/MethodSelectorResolver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.reflect;
+
+import reactor.function.Function;
+import reactor.event.selector.Selector;
+import reactor.support.Supports;
+
+import java.lang.reflect.Method;
+
+/**
+ * When given a {@link Method}, a {@code MethodSelectorResolver} will attempt to return a
+ * {@link Selector} for the method.
+ *
+ * @author Jon Brisbin
+ */
+public interface MethodSelectorResolver extends Supports<Method>, Function<Method, Selector> {
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/reflect/SimpleMethodNotificationKeyResolver.java b/reactor-core/src/main/java/reactor/core/dynamic/reflect/SimpleMethodNotificationKeyResolver.java
new file mode 100644
index 0000000..495206a
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/reflect/SimpleMethodNotificationKeyResolver.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.reflect;
+
+import static reactor.core.dynamic.reflect.support.MethodNameUtils.methodNameToNotificationKey;
+
+import java.lang.reflect.Method;
+
+import reactor.core.dynamic.annotation.Notify;
+import reactor.core.dynamic.reflect.support.AnnotationUtils;
+
+/**
+ * An implementation of {@link MethodNotificationKeyResolver} that looks for an {@link Notify} annotation
+ * or uses the method name minus the "notify" portion and lower-casing the first character.
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ */
+public final class SimpleMethodNotificationKeyResolver implements MethodNotificationKeyResolver {
+
+	@Override
+	public boolean supports(Method method) {
+		return method.getDeclaringClass() != Object.class && !method.getName().contains("$");
+	}
+
+	@Override
+	public String apply(Method method) {
+		String key;
+		Notify notifyAnno = AnnotationUtils.find(method, Notify.class);
+
+		if (null != notifyAnno) {
+			key = notifyAnno.value();
+		} else {
+			key = methodNameToNotificationKey(method.getName());
+		}
+
+		return key;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/reflect/SimpleMethodSelectorResolver.java b/reactor-core/src/main/java/reactor/core/dynamic/reflect/SimpleMethodSelectorResolver.java
new file mode 100644
index 0000000..f380496
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/reflect/SimpleMethodSelectorResolver.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.reflect;
+
+import static reactor.core.dynamic.reflect.support.MethodNameUtils.methodNameToSelectorName;
+
+import java.lang.reflect.Method;
+
+import reactor.core.dynamic.annotation.On;
+import reactor.core.dynamic.reflect.support.AnnotationUtils;
+import reactor.event.selector.ObjectSelector;
+import reactor.event.selector.Selector;
+
+/**
+ * An implementation of {@link MethodSelectorResolver} that looks for an {@link On} annotation
+ * or uses the method name minus the "on" portion and lower-casing the first character.
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ */
+public class SimpleMethodSelectorResolver implements MethodSelectorResolver {
+
+	@Override
+	public Selector apply(Method method) {
+		String sel;
+		On onAnno = AnnotationUtils.find(method, On.class);
+		if (null != onAnno) {
+			sel = onAnno.value();
+		} else {
+			sel = methodNameToSelectorName(method.getName());
+		}
+
+		return (!"".equals(sel) ? new ObjectSelector<String>(sel) : null);
+	}
+
+	@Override
+	public boolean supports(Method method) {
+		return method.getDeclaringClass() != Object.class && !method.getName().contains("$");
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/reflect/package-info.java b/reactor-core/src/main/java/reactor/core/dynamic/reflect/package-info.java
new file mode 100644
index 0000000..7c12c2f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/reflect/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Reflection-based tools to provide dynamic proxies that take care of the logistics of wiring a Reactor and
+ * publishing events.
+ */
+package reactor.core.dynamic.reflect;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/AnnotationUtils.java b/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/AnnotationUtils.java
new file mode 100644
index 0000000..227290b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/AnnotationUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.reflect.support;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+
+/**
+ * Utility methods for working with Annotations.
+ *
+ * @author Andy Wilkinson
+ * @author Jon Brisbin
+ *
+ */
+public final class AnnotationUtils {
+
+	private AnnotationUtils() {
+
+	}
+
+	/**
+	 * Finds the annotation of the given {@code type} on the given {@code method}.
+	 *
+	 * @param m The Method to find the annotation an
+	 * @param type The type of annotation to find
+	 *
+	 * @return The annotation that was found, or {@code null}.
+	 */
+	@SuppressWarnings("unchecked")
+	public static <A extends Annotation> A find(Method m, Class<A> type) {
+		if (m.getDeclaredAnnotations().length > 0) {
+			for (Annotation anno : m.getDeclaredAnnotations()) {
+				if (type.isAssignableFrom(anno.getClass())) {
+					return (A) anno;
+				}
+			}
+		}
+		return null;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/MethodNameUtils.java b/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/MethodNameUtils.java
new file mode 100644
index 0000000..dbff542
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/MethodNameUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic.reflect.support;
+
+/**
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ */
+public abstract class MethodNameUtils {
+
+	protected MethodNameUtils() {
+	}
+
+	/**
+	 * Strip the "on" or "notify" when a method name and split the camel-case into a dot-separated {@literal String}.
+	 *
+	 * @param name The method name to transform.
+	 * @return A camel-case-to-dot-separated version suitable for a {@link reactor.event.selector.Selector}.
+	 */
+	public static String methodNameToSelectorName(String name) {
+		return doTransformation(name, "on", "^on\\.");
+	}
+
+	/**
+	 * Strip the "notify" when a method name and split the camel-case into a dot-separated {@literal String}.
+	 *
+	 * @param name The method name to transform.
+	 * @return A camel-case-to-dot-separated version suitable for a notification key
+	 */
+	public static String methodNameToNotificationKey(String name) {
+		return doTransformation(name, "notify", "^notify\\.");
+	}
+
+	private static String doTransformation(String name, String prefix, String replacementRegex) {
+		String s = name.replaceAll("([A-Z])", ".$1").toLowerCase();
+		if (s.startsWith(prefix)) {
+			return s.replaceFirst(replacementRegex, "");
+		} else {
+			return null;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/package-info.java b/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/package-info.java
new file mode 100644
index 0000000..d2e2954
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/dynamic/reflect/support/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Support classes to aid in creating reflection-based {@link reactor.core.dynamic.DynamicReactor DynamicReactors}.
+ */
+package reactor.core.dynamic.reflect.support;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/fork/ForkJoinPool.java b/reactor-core/src/main/java/reactor/core/fork/ForkJoinPool.java
new file mode 100644
index 0000000..7bf75cb
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/fork/ForkJoinPool.java
@@ -0,0 +1,122 @@
+package reactor.core.fork;
+
+import com.gs.collections.api.list.ImmutableList;
+import com.gs.collections.impl.list.mutable.FastList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.core.composable.spec.Promises;
+import reactor.core.composable.spec.Streams;
+import reactor.event.dispatch.Dispatcher;
+import reactor.function.Function;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Simple {@link java.util.concurrent.Executor} backed fork/join pool that will coalesce results from asynchronous tasks
+ * and publish them into a {@link reactor.core.composable.Promise} or a {@link reactor.core.composable.Stream} depending
+ * on whether you intend to {@code fork()} an execution or {@code join()} them together. tasks
+ *
+ * @author Jon Brisbin
+ */
+public class ForkJoinPool {
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+
+	private final Environment env;
+	private final Dispatcher  dispatcher;
+	private final Executor    executor;
+
+	public ForkJoinPool(Environment env) {
+		this(env, env.getDefaultDispatcher(), env.getDispatcher(Environment.THREAD_POOL));
+	}
+
+	public ForkJoinPool(Environment env,
+	                    Dispatcher dispatcher,
+	                    Executor executor) {
+		this.env = env;
+		this.dispatcher = dispatcher;
+		this.executor = executor;
+	}
+
+	/**
+	 * Asynchronously submit the given tasks, one submit per task, to the configured {@link java.util.concurrent.Executor}
+	 * and collecting the results in a {@link java.util.List} that will be the fulfillment of the {@link
+	 * reactor.core.composable.Promise} returned from {@link ForkJoinTask#compose()}.
+	 *
+	 * @param tasks
+	 * 		asynchronous tasks to execute
+	 * @param <V>
+	 * 		type of task result
+	 *
+	 * @return fork/join task
+	 */
+	public <V> ForkJoinTask<ImmutableList<V>, Promise<ImmutableList<V>>> join(final Function<?, V>... tasks) {
+		return join(Arrays.asList(tasks));
+	}
+
+	/**
+	 * Asynchronously submit the given tasks, one submit per task, to the configured {@link java.util.concurrent.Executor}
+	 * and collecting the results in a {@link java.util.List} that will be the fulfillment of the {@link
+	 * reactor.core.composable.Promise} returned from {@link ForkJoinTask#compose()}.
+	 *
+	 * @param tasks
+	 * 		asynchronous tasks to execute
+	 * @param <V>
+	 * 		type of task result
+	 *
+	 * @return fork/join task
+	 */
+	public <V> ForkJoinTask<ImmutableList<V>, Promise<ImmutableList<V>>> join(final Collection<Function<?, V>> tasks) {
+		final Deferred<ImmutableList<V>, Promise<ImmutableList<V>>> d
+				= Promises.defer(env, dispatcher);
+		final ForkJoinTask<ImmutableList<V>, Promise<ImmutableList<V>>> t
+				= new ForkJoinTask<ImmutableList<V>, Promise<ImmutableList<V>>>(executor, d);
+
+		final AtomicInteger count = new AtomicInteger(tasks.size());
+		final FastList<V> results = FastList.newList();
+
+		for (final Function fn : tasks) {
+			t.add(new Function<Object, ImmutableList<V>>() {
+				@SuppressWarnings("unchecked")
+				@Override
+				public ImmutableList<V> apply(Object o) {
+					try {
+						V result = (V) fn.apply(o);
+						synchronized (results) {
+							results.add(result);
+						}
+					} finally {
+						if (count.decrementAndGet() == 0) {
+							d.accept(results.toImmutable());
+						}
+					}
+					return null;
+				}
+			});
+		}
+
+		return t;
+	}
+
+	/**
+	 * Asynchronously execute tasks added to the returned {@link reactor.core.fork.ForkJoinTask} and publish the non-null
+	 * results, one per task, to the {@link Stream} returned from {@link ForkJoinTask#compose()}.
+	 *
+	 * @param <V>
+	 * 		type of task result
+	 *
+	 * @return fork/join task
+	 */
+	public <V> ForkJoinTask<V, Stream<V>> fork() {
+		Deferred<V, Stream<V>> d = Streams.defer(env, dispatcher);
+		return new ForkJoinTask<V, Stream<V>>(executor, d);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/fork/ForkJoinTask.java b/reactor-core/src/main/java/reactor/core/fork/ForkJoinTask.java
new file mode 100644
index 0000000..96c6d3d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/fork/ForkJoinTask.java
@@ -0,0 +1,118 @@
+package reactor.core.fork;
+
+import com.gs.collections.api.list.MutableList;
+import com.gs.collections.impl.block.procedure.checked.CheckedProcedure;
+import com.gs.collections.impl.list.mutable.MultiReaderFastList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.composable.Composable;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.function.Consumer;
+import reactor.function.Function;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents a collection of asynchronous tasks that will be executed once per {@link #submit()}.
+ *
+ * @author Jon Brisbin
+ */
+public class ForkJoinTask<T, C extends Composable<T>> implements Consumer<Object> {
+
+	private final Logger                              log   = LoggerFactory.getLogger(getClass());
+	private final MultiReaderFastList<Function<?, ?>> tasks = MultiReaderFastList.newList();
+
+	private final Executor       executor;
+	private final Deferred<T, C> deferred;
+
+	ForkJoinTask(Executor executor, Deferred<T, C> deferred) {
+		this.executor = executor;
+		this.deferred = deferred;
+	}
+
+	/**
+	 * Get the {@link com.gs.collections.api.list.MutableList} of tasks that will be submitted when {@link
+	 * #submit(Object)} is called.
+	 *
+	 * @return
+	 */
+	public MutableList<Function<?, ?>> getTasks() {
+		return tasks;
+	}
+
+	/**
+	 * Add a task to the collection of tasks to be executed.
+	 *
+	 * @param fn
+	 * 		task to submit
+	 * @param <V>
+	 * 		type of result
+	 *
+	 * @return {@code this}
+	 */
+	public <V> ForkJoinTask<T, C> add(Function<V, T> fn) {
+		tasks.add(fn);
+		return this;
+	}
+
+	/**
+	 * Compose actions against the asynchronous results.
+	 *
+	 * @return {@link reactor.core.composable.Promise} or a {@link reactor.core.composable.Stream} depending on the
+	 * implementation.
+	 */
+	public C compose() {
+		return deferred.compose();
+	}
+
+	/**
+	 * Submit all configured tasks, possibly in parallel (depending on the configuration of the {@link
+	 * java.util.concurrent.Executor} in use), passing {@code null} as an input parameter.
+	 */
+	public void submit() {
+		accept(null);
+	}
+
+	/**
+	 * Submit all configured tasks, possibly in parallel (depending on the configuration of the {@link
+	 * java.util.concurrent.Executor} in use), passing {@code arg} as an input parameter.
+	 *
+	 * @param arg
+	 * 		input parameter
+	 * @param <V>
+	 * 		type of input parameter
+	 */
+	public <V> void submit(V arg) {
+		accept(arg);
+	}
+
+	@Override
+	public void accept(final Object arg) {
+		tasks.forEach(new CheckedProcedure<Function>() {
+			@SuppressWarnings("unchecked")
+			@Override
+			public void safeValue(final Function fn) throws Exception {
+				executor.execute(new Runnable() {
+					@Override
+					public void run() {
+						try {
+							Object result = fn.apply(arg);
+							if (null != result) {
+								deferred.accept((T) result);
+							}
+						} catch (Exception e) {
+							if (compose() instanceof Stream || !((Promise) compose()).isComplete()) {
+								deferred.accept(e);
+							} else {
+								log.error(e.getMessage(), e);
+							}
+						}
+					}
+				});
+			}
+		});
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/package-info.java b/reactor-core/src/main/java/reactor/core/package-info.java
new file mode 100644
index 0000000..19c15b6
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Core components of the Reactor framework.
+ */
+package reactor.core;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/processor/Operation.java b/reactor-core/src/main/java/reactor/core/processor/Operation.java
new file mode 100644
index 0000000..40b5895
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/processor/Operation.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.processor;
+
+import reactor.function.Supplier;
+
+/**
+ * A {@link Operation} represents the payload that gets (re) used in the processor.
+ *
+ * The {@link Operation} itself gets allocated during creation time for the {@link Processor}, so at
+ * runtime the instance will be reused. What changes during runtime is the enclosed payload.
+ * </p>
+ * In addition to the payload, the {@link #id} represents the corresponding ID in the underlying
+ * data structure (in the general case a {@link com.lmax.disruptor.RingBuffer}.
+ *
+ * @param <T> the type of the supplied object
+ *
+ * @author Jon Brisbin
+ */
+public abstract class Operation<T> implements Supplier<T> {
+
+	protected volatile Long id;
+	private final      T    data;
+
+  /**
+   * Create a new {@link Operation} and apply the given payload.
+   *
+   * @param data the payload to store.
+   */
+	Operation(T data) {
+		this.data = data;
+	}
+
+  /**
+   * Set the identifier for the underlying payload.
+   *
+   * @param id the identifier, most likely a sequnce number.
+   */
+	void setId(Long id) {
+		this.id = id;
+	}
+
+  /**
+   * Get the {@link Operation} payload.
+   *
+   * @return the enclosed payload.
+   */
+	@Override public T get() {
+		return data;
+	}
+
+  /**
+   * Commit the {@link Operation} to the underlying datastructure.
+   */
+	public abstract void commit();
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/processor/Processor.java b/reactor-core/src/main/java/reactor/core/processor/Processor.java
new file mode 100644
index 0000000..0101cdb
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/processor/Processor.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.processor;
+
+import com.lmax.disruptor.*;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+import reactor.event.registry.Registration;
+import reactor.event.registry.Registry;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+import reactor.function.batch.BatchConsumer;
+import reactor.support.NamedDaemonThreadFactory;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * A {@code Processor} is a highly-efficient data processor that is backed by an <a
+ * href="https://github.com/LMAX-Exchange/disruptor">LMAX Disruptor RingBuffer</a>.
+ * <p>
+ * Rather than dealing with dynamic {@link Consumer} registration and event routing, the {@code Processor} gains its
+ * extreme efficiency from pre-allocating the data objects (for which you pass a {@link Supplier} whose job is to
+ * provide new instances of the data class on startup) and by baking-in a {@code Consumer} for published events.
+ * <p>
+ * The {@code Processor} can be used in two different modes: single-operation or batch. For single operations, use the
+ * {@link #prepare()} method to allocate an object from the {@link RingBuffer}. An {@link Operation} is also a {@link
+ * Supplier}, so call {@link reactor.function.Supplier#get()} to get access to the data object. One can then update the
+ * members on the data object and, by calling {@link reactor.core.processor.Operation#commit()}, publish the event into
+ * the {@link RingBuffer} to be handled by the {@link Consumer}.
+ * <p>
+ * To operate on the {@code Processor} in batch mode, first set a {@link BatchConsumer} as the {@link Consumer} of
+ * events. This interface provides two additional methods, {@link reactor.function.batch.BatchConsumer#start()}, which
+ * is invoked before the batch starts, and {@link reactor.function.batch.BatchConsumer#end()}, which is invoked when
+ * the
+ * batch is submitted. The {@link BatchConsumer} will work for either single-operation mode or batch mode, but only a
+ * {@link BatchConsumer} will be able to recognize the start and end of a batch.
+ *
+ * @author Jon Brisbin
+ * @see <a href="https://github.com/LMAX-Exchange/disruptor">https://github.com/LMAX-Exchange/disruptor</a>
+ */
+public class Processor<T> implements Supplier<Operation<T>> {
+
+	private final int                      opsBufferSize;
+	private final ExecutorService          executor;
+	private final Disruptor<Operation<T>>  disruptor;
+	private final RingBuffer<Operation<T>> ringBuffer;
+
+	@SuppressWarnings("unchecked")
+	public Processor(@Nonnull final Supplier<T> dataSupplier,
+	                 @Nonnull final Consumer<T> consumer,
+	                 @Nonnull Registry<Consumer<Throwable>> errorConsumers,
+	                 WaitStrategy waitStrategy,
+	                 boolean multiThreadedProducer,
+	                 int opsBufferSize) {
+		Assert.notNull(dataSupplier, "Data Supplier cannot be null.");
+		Assert.notNull(consumer, "Consumer cannot be null.");
+		Assert.notNull(errorConsumers, "Error Consumers Registry cannot be null.");
+
+		executor = Executors.newCachedThreadPool(new NamedDaemonThreadFactory("processor"));
+
+		if (opsBufferSize < 1) {
+			this.opsBufferSize = 256 * Runtime.getRuntime().availableProcessors();
+		} else {
+			this.opsBufferSize = opsBufferSize;
+		}
+
+		disruptor = new Disruptor<Operation<T>>(
+				new EventFactory<Operation<T>>() {
+					@SuppressWarnings("rawtypes")
+					@Override
+					public Operation<T> newInstance() {
+						return new Operation<T>(dataSupplier.get()) {
+							@Override
+							public void commit() {
+								ringBuffer.publish(id);
+							}
+						};
+					}
+				},
+				this.opsBufferSize,
+				executor,
+				(multiThreadedProducer ? ProducerType.MULTI : ProducerType.SINGLE),
+				(waitStrategy != null ? waitStrategy : new BlockingWaitStrategy())
+		);
+
+		disruptor.handleExceptionsWith(new ConsumerExceptionHandler(errorConsumers));
+		disruptor.handleEventsWith(new ConsumerEventHandler<T>(consumer));
+
+		ringBuffer = disruptor.start();
+	}
+
+	/**
+	 * Shutdown this {@code Processor} by shutting down the thread pool.
+	 */
+	public void shutdown() {
+		executor.shutdown();
+		disruptor.shutdown();
+	}
+
+	/**
+	 * Prepare an {@link Operation} by allocating it from the buffer and preparing it to be submitted once {@link
+	 * reactor.core.processor.Operation#commit()} is invoked.
+	 *
+	 * @return an {@link Operation} instance allocated from the buffer
+	 */
+	public Operation<T> prepare() {
+		long seqId = ringBuffer.next();
+		Operation<T> op = ringBuffer.get(seqId);
+		op.setId(seqId);
+
+		return op;
+	}
+
+	/**
+	 * Commit a list of {@link reactor.core.processor.Operation Operations} by doing a batch publish.
+	 *
+	 * @param ops the {@code Operations} to commit
+	 * @return {@literal this}
+	 */
+	public Processor<T> commit(List<Operation<T>> ops) {
+		if (null == ops || ops.isEmpty()) {
+			return this;
+		}
+
+		Operation<T> first = ops.get(0);
+		Operation<T> last = ops.get(ops.size() - 1);
+
+		long firstSeqId = first.id;
+		long lastSeqId = last.id;
+
+		if (lastSeqId > firstSeqId) {
+			ringBuffer.publish(firstSeqId, lastSeqId);
+		} else {
+			ringBuffer.publish(firstSeqId);
+		}
+
+		return this;
+	}
+
+	/**
+	 * If the {@link Consumer} set in the spec is a {@link BatchConsumer}, then the start method will be invoked before
+	 * the batch is published, then all the events of the batch are published to the consumer, then the batch end is
+	 * published.
+	 * <p>
+	 * The {@link Consumer} passed here is a mutator. Rather than accessing the data object directly, as one would do
+	 * with a {@link #prepare()} call, pass a {@link Consumer} here that will accept the allocated data object which one
+	 * can update with the appropriate data. Note that this is not an event handler. The event handler {@link Consumer}
+	 * is
+	 * specified in the spec (which is passed into the {@code Processor} constructor).
+	 *
+	 * @param size    size of the batch
+	 * @param mutator a {@link Consumer} that mutates the data object before it is published as an event and handled by the
+	 *                event {@link Consumer}
+	 * @return {@literal this}
+	 */
+	public Processor<T> batch(int size, Consumer<T> mutator) {
+		long start = -1;
+		long end = 0;
+		for (int i = 0; i < size; i++) {
+			if (i > 0 && i % opsBufferSize == 0) {
+				// Automatically flush when buffer is full
+				ringBuffer.publish(start, end);
+				start = -1;
+			}
+			long l = ringBuffer.next();
+			if (start < 0) {
+				start = l;
+			}
+			end = l;
+			mutator.accept(ringBuffer.get(end).get());
+		}
+		ringBuffer.publish(start, end);
+
+		return this;
+	}
+
+	@Override
+	public Operation<T> get() {
+		return prepare();
+	}
+
+	private static class ConsumerEventHandler<T> implements EventHandler<Operation<T>>, LifecycleAware {
+		final Consumer<T> consumer;
+		final boolean     isBatchConsumer;
+
+		private ConsumerEventHandler(Consumer<T> consumer) {
+			this.consumer = consumer;
+			this.isBatchConsumer = consumer instanceof BatchConsumer;
+		}
+
+		@Override
+		public void onStart() {
+			if (isBatchConsumer) {
+				((BatchConsumer) consumer).start();
+			}
+		}
+
+		@Override
+		public void onShutdown() {
+			if (isBatchConsumer) {
+				((BatchConsumer) consumer).end();
+			}
+		}
+
+		@Override
+		public void onEvent(Operation<T> op, long sequence, boolean endOfBatch) throws Exception {
+			consumer.accept(op.get());
+		}
+	}
+
+	private static class ConsumerExceptionHandler implements ExceptionHandler {
+		final Registry<Consumer<Throwable>> errorConsumers;
+
+		private ConsumerExceptionHandler(Registry<Consumer<Throwable>> errorConsumers) {
+			this.errorConsumers = errorConsumers;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public void handleEventException(Throwable ex, long sequence, Object event) {
+			for (Registration<? extends Consumer<? extends Throwable>> reg : errorConsumers.select(ex.getClass())) {
+				((Consumer<Throwable>) reg.getObject()).accept(ex);
+			}
+		}
+
+		@Override
+		public void handleOnStartException(Throwable ex) {
+			handleEventException(ex, -1, null);
+		}
+
+		@Override
+		public void handleOnShutdownException(Throwable ex) {
+			handleEventException(ex, -1, null);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/processor/package-info.java b/reactor-core/src/main/java/reactor/core/processor/package-info.java
new file mode 100644
index 0000000..f4d8f17
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/processor/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * A {@link reactor.core.processor.Processor} is a thin wrapper around the LMAX Disruptor RingBuffer.
+ * @see <a href="http://lmax-exchange.github.io/disruptor/">http://lmax-exchange.github.io/disruptor/</a>
+ */
+package reactor.core.processor;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/processor/spec/ProcessorSpec.java b/reactor-core/src/main/java/reactor/core/processor/spec/ProcessorSpec.java
new file mode 100644
index 0000000..ab61484
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/processor/spec/ProcessorSpec.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.processor.spec;
+
+import com.lmax.disruptor.*;
+import reactor.core.processor.Processor;
+import reactor.event.registry.CachingRegistry;
+import reactor.event.registry.Registry;
+import reactor.event.selector.Selectors;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+import reactor.util.Assert;
+
+/**
+ * Specification class to create {@link Processor Processors}.
+ *
+ * @author Jon Brisbin
+ */
+public class ProcessorSpec<T> implements Supplier<Processor<T>> {
+
+	private Registry<Consumer<Throwable>> errorConsumers        = new CachingRegistry<Consumer<Throwable>>();
+	private boolean                       multiThreadedProducer = false;
+	private int                           dataBufferSize        = -1;
+	private WaitStrategy                  waitStrategy          = null;
+	private Supplier<T> dataSupplier;
+	private Consumer<T> consumer;
+
+	/**
+	 * Protect against publication of data events from multiple producer threads.
+	 *
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> multiThreadedProducer() {
+		this.multiThreadedProducer = true;
+		return this;
+	}
+
+	/**
+	 * Optimize for highest throughput by assuming only a single thread will be publishing data events into this {@code
+	 * Processor}.
+	 *
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> singleThreadedProducer() {
+		this.multiThreadedProducer = false;
+		return this;
+	}
+
+	/**
+	 * How many data objects to pre-allocate in the buffer.
+	 *
+	 * @param dataBufferSize number of data objects to pre-allocate
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> dataBufferSize(int dataBufferSize) {
+		this.dataBufferSize = dataBufferSize;
+		return this;
+	}
+
+	/**
+	 * Use the given {@link Supplier} to provide new instances of the data object for pre-allocation.
+	 *
+	 * @param dataSupplier the {@link Supplier} to provide new data instances
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> dataSupplier(Supplier<T> dataSupplier) {
+		Assert.isNull(this.dataSupplier, "Data Supplier is already set.");
+		this.dataSupplier = dataSupplier;
+		return this;
+	}
+
+	/**
+	 * Set Disruptor's {@link com.lmax.disruptor.WaitStrategy}.
+	 *
+	 * @param waitStrategy the {@link com.lmax.disruptor.WaitStrategy} to use
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> waitStrategy(WaitStrategy waitStrategy) {
+		this.waitStrategy = waitStrategy;
+		return this;
+	}
+
+	/**
+	 * Set {@link com.lmax.disruptor.BlockingWaitStrategy} as wait strategy.
+	 *
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> blockingWaitStrategy() {
+		this.waitStrategy = new BlockingWaitStrategy();
+		return this;
+	}
+
+	/**
+	 * Set {@link com.lmax.disruptor.SleepingWaitStrategy} as wait strategy.
+	 *
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> sleepingWaitStrategy() {
+		this.waitStrategy = new SleepingWaitStrategy();
+		return this;
+	}
+
+
+	/**
+	 * Set {@link com.lmax.disruptor.YieldingWaitStrategy} as wait strategy.
+	 *
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> yieldingWaitStrategy() {
+		this.waitStrategy = new YieldingWaitStrategy();
+		return this;
+	}
+
+	/**
+	 * Set {@link com.lmax.disruptor.BusySpinWaitStrategy} as wait strategy.
+	 *
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> busySpinWaitStrategy() {
+		this.waitStrategy = new BusySpinWaitStrategy();
+		return this;
+	}
+
+	/**
+	 * When data is mutated and published into the {@code Processor}, invoke the given {@link Consumer} and pass the
+	 * mutated data.
+	 *
+	 * @param consumer the mutated event data {@code Consumer}
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> consume(Consumer<T> consumer) {
+		this.consumer = consumer;
+		return this;
+	}
+
+	/**
+	 * Assign the given {@link Consumer} as an error handler for exceptions of the given type.
+	 *
+	 * @param type          type of the exception to handle
+	 * @param errorConsumer exception {@code Consumer}
+	 * @return {@literal this}
+	 */
+	public ProcessorSpec<T> when(Class<? extends Throwable> type, Consumer<Throwable> errorConsumer) {
+		errorConsumers.register(Selectors.type(type), errorConsumer);
+		return this;
+	}
+
+
+	@Override
+	public Processor<T> get() {
+		return new Processor<T>(dataSupplier,
+		                        consumer,
+		                        errorConsumers,
+		                        waitStrategy,
+		                        multiThreadedProducer,
+		                        dataBufferSize);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/processor/spec/package-info.java b/reactor-core/src/main/java/reactor/core/processor/spec/package-info.java
new file mode 100644
index 0000000..57c351d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/processor/spec/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Specs help create {@link reactor.core.processor.Processor Processors} by providing a fluent API to specify
+ * common options.
+ */
+package reactor.core.processor.spec;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/spec/ReactorSpec.java b/reactor-core/src/main/java/reactor/core/spec/ReactorSpec.java
new file mode 100644
index 0000000..fdd6059
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/spec/ReactorSpec.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.spec;
+
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.spec.support.EventRoutingComponentSpec;
+
+/**
+ * A helper class for configuring a new {@link Reactor}.
+ *
+ * @author Jon Brisbin
+ */
+public class ReactorSpec extends EventRoutingComponentSpec<ReactorSpec, Reactor> {
+
+	@Override
+	protected final Reactor configure(Reactor reactor, Environment environment) {
+		return reactor;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/spec/Reactors.java b/reactor-core/src/main/java/reactor/core/spec/Reactors.java
new file mode 100644
index 0000000..b0bf91b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/spec/Reactors.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.spec;
+
+import reactor.core.Environment;
+import reactor.core.Observable;
+import reactor.core.Reactor;
+import reactor.event.Event;
+import reactor.event.dispatch.Dispatcher;
+import reactor.function.Consumer;
+import reactor.tuple.Tuple;
+
+/**
+ * Base class to encapsulate commonly-used functionality around Reactors.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+public abstract class Reactors {
+
+	/**
+	 * Create a new {@link ReactorSpec} to configure a Reactor.
+	 *
+	 * @return The Reactor spec
+	 */
+	public static ReactorSpec reactor() {
+		return new ReactorSpec();
+	}
+
+	/**
+	 * Create a new {@link reactor.core.Reactor} using the given {@link reactor.core.Environment}.
+	 *
+	 * @param env
+	 * 		The {@link reactor.core.Environment} to use.
+	 *
+	 * @return A new {@link reactor.core.Reactor}
+	 */
+	public static Reactor reactor(Environment env) {
+		return new ReactorSpec().env(env).dispatcher(env.getDefaultDispatcher()).get();
+	}
+
+	/**
+	 * Create a new {@link reactor.core.Reactor} using the given {@link reactor.core.Environment} and dispatcher name.
+	 *
+	 * @param env
+	 * 		The {@link reactor.core.Environment} to use.
+	 * @param dispatcher
+	 * 		The name of the {@link reactor.event.dispatch.Dispatcher} to use.
+	 *
+	 * @return A new {@link reactor.core.Reactor}
+	 */
+	public static Reactor reactor(Environment env, String dispatcher) {
+		return new ReactorSpec().env(env).dispatcher(dispatcher).get();
+	}
+
+	/**
+	 * Create a new {@link reactor.core.Reactor} using the given {@link reactor.core.Environment} and {@link
+	 * reactor.event.dispatch.Dispatcher}.
+	 *
+	 * @param env
+	 * 		The {@link reactor.core.Environment} to use.
+	 * @param dispatcher
+	 * 		The {@link reactor.event.dispatch.Dispatcher} to use.
+	 *
+	 * @return A new {@link reactor.core.Reactor}
+	 */
+	public static Reactor reactor(Environment env, Dispatcher dispatcher) {
+		return new ReactorSpec().env(env).dispatcher(dispatcher).get();
+	}
+
+	/**
+	 * Schedule an arbitrary {@link reactor.function.Consumer} to be executed on the given {@link
+	 * reactor.core.Observable}, passing the given {@link
+	 * reactor.event.Event}.
+	 *
+	 * @param consumer
+	 * 		The {@link reactor.function.Consumer} to invoke.
+	 * @param data
+	 * 		The data to pass to the consumer.
+	 * @param observable
+	 * 		The {@literal Observable} that will be used to invoke the {@literal Consumer}
+	 * @param <T>
+	 * 		The type of the data.
+	 * 	@deprecated @since 1.1
+	 */
+	@Deprecated
+	public static <T> void schedule(final Consumer<T> consumer, T data, Observable observable) {
+		((Reactor)observable).schedule(consumer, data);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/spec/package-info.java b/reactor-core/src/main/java/reactor/core/spec/package-info.java
new file mode 100644
index 0000000..fe7d78f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/spec/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Specs help create {@link reactor.core.Reactor Reactors} by providing a fluent API to specify
+ * common options.
+ */
+package reactor.core.spec;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/spec/support/DispatcherComponentSpec.java b/reactor-core/src/main/java/reactor/core/spec/support/DispatcherComponentSpec.java
new file mode 100644
index 0000000..137485d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/spec/support/DispatcherComponentSpec.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.spec.support;
+
+import reactor.core.Environment;
+import reactor.event.dispatch.ActorDispatcher;
+import reactor.event.dispatch.Dispatcher;
+import reactor.event.dispatch.SynchronousDispatcher;
+import reactor.function.Function;
+import reactor.function.Supplier;
+
+/**
+ * A generic environment-aware class for specifying components that need to be configured
+ * with an {@link Environment} and {@link Dispatcher}.
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ *
+ * @param <SPEC> The DispatcherComponentSpec subclass
+ * @param <TARGET> The type that this spec will create
+ */
+ at SuppressWarnings("unchecked")
+public abstract class DispatcherComponentSpec<SPEC extends DispatcherComponentSpec<SPEC, TARGET>, TARGET> implements Supplier<TARGET> {
+
+	private Environment env;
+	private Dispatcher  dispatcher;
+
+	/**
+	 * Configures the spec, and potentially the component being configured, to use the given
+	 * environment
+	 *
+	 * @param env The environment to use
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC env(Environment env) {
+		this.env = env;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component to use an Environment based ActorDispatcher. An Actor Dispatcher assigns a unique key
+	 * to a dispatcher provided by a mapping function. In this case, each Dispatcher mapper call will delegate to
+	 * {@link Environment#getDispatcher(String)}, returning a new dispatcher from the pool using round robin selection.
+	 *
+	 * @param name The dispatcher to use
+	 *
+	 * @return {@code this}
+	 *
+	 * @throws IllegalStateException if no Environment has been configured
+	 *
+	 * @see Environment#getDefaultDispatcher()
+	 * @see #env(Environment)
+	 */
+	public final SPEC actorSystem(final String name) {
+		assertNonNullEnvironment("Cannot create an Environment ActorDispatcher without an Environment");
+		actorSystem(new Function<Object,Dispatcher>(){
+			@Override
+			public Dispatcher apply(Object o) {
+				return env.getDispatcher(name);
+			}
+		});
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component to use an ActorDispatcher. An Actor Dispatcher assigns a unique key
+	 * to a dispatcher provided by a mapping function.
+	 *
+	 * @param actorMapper The dispatcher to use
+	 *
+	 * @return {@code this}
+	 *
+	 * @throws IllegalStateException if no Environment has been configured
+	 *
+	 * @see Environment#getDefaultDispatcher()
+	 * @see #env(Environment)
+	 */
+	public final SPEC actorSystem(Function<Object,Dispatcher> actorMapper) {
+		this.dispatcher = new ActorDispatcher(actorMapper);
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component to use the configured Environment's default dispatcher
+	 *
+	 * @return {@code this}
+	 *
+	 * @throws IllegalStateException if no Environment has been configured
+	 *
+	 * @see Environment#getDefaultDispatcher()
+	 * @see #env(Environment)
+	 */
+	public final SPEC defaultDispatcher() {
+		assertNonNullEnvironment("Cannot use the default Dispatcher without an Environment");
+		this.dispatcher = env.getDefaultDispatcher();
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component to use a synchronous dispatcher
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC synchronousDispatcher() {
+		this.dispatcher = new SynchronousDispatcher();
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component to use the given {@code dispatcher}
+	 *
+	 * @param dispatcher The dispatcher to use
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC dispatcher(Dispatcher dispatcher) {
+		this.dispatcher = dispatcher;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component to the dispatcher in the configured Environment with the given
+	 * {@code dispatcherName}
+	 *
+	 * @param dispatcherName The name of the dispatcher
+	 *
+	 * @return {@code this}
+	 *
+	 * @throws IllegalStateException if no Environment has been configured
+	 *
+	 * @see Environment#getDispatcher(String)
+	 * @see #env(Environment)
+	 */
+	public final SPEC dispatcher(String dispatcherName) {
+		assertNonNullEnvironment("Cannot reference a Dispatcher by name without an Environment");
+		this.dispatcher = env.getDispatcher(dispatcherName);
+		return (SPEC) this;
+	}
+
+	@Override
+	public final TARGET get() {
+		return configure(getDispatcher(), this.env);
+	}
+
+	private Dispatcher getDispatcher() {
+		if (this.dispatcher != null) {
+			return this.dispatcher;
+		} else if (env != null) {
+			return env.getDefaultDispatcher();
+		} else {
+			return new SynchronousDispatcher();
+		}
+	}
+
+	private void assertNonNullEnvironment(String message) {
+		if (env == null) {
+			throw new IllegalStateException(message);
+		}
+	}
+
+	protected abstract TARGET configure(Dispatcher dispatcher, Environment environment);
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/spec/support/EventRoutingComponentSpec.java b/reactor-core/src/main/java/reactor/core/spec/support/EventRoutingComponentSpec.java
new file mode 100644
index 0000000..807f9b5
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/spec/support/EventRoutingComponentSpec.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.spec.support;
+
+import reactor.convert.Converter;
+import reactor.convert.DelegatingConverter;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.event.Event;
+import reactor.event.dispatch.Dispatcher;
+import reactor.event.dispatch.TraceableDelegatingDispatcher;
+import reactor.event.registry.CachingRegistry;
+import reactor.event.registry.Registry;
+import reactor.event.routing.*;
+import reactor.event.selector.Selector;
+import reactor.filter.*;
+import reactor.function.Consumer;
+import reactor.util.Assert;
+
+import java.util.List;
+
+
+/**
+ * A generic environment-aware class for specifying components that need to be configured
+ * with an {@link Environment}, {@link Dispatcher}, and {@link EventRouter}.
+ *
+ * @param <SPEC>   The DispatcherComponentSpec subclass
+ * @param <TARGET> The type that this spec will create
+ * @author Jon Brisbin
+ */
+ at SuppressWarnings("unchecked")
+public abstract class EventRoutingComponentSpec<SPEC extends EventRoutingComponentSpec<SPEC, TARGET>, TARGET> extends
+                                                                                                              DispatcherComponentSpec<SPEC, TARGET> {
+
+	private Converter                              converter;
+	private EventRoutingStrategy                   eventRoutingStrategy;
+	private EventRouter                            eventRouter;
+	private ConsumerInvoker                        consumerInvoker;
+	private Filter                                 eventFilter;
+	private Consumer<Throwable>                    dispatchErrorHandler;
+	private Consumer<Throwable>                    uncaughtErrorHandler;
+	private Selector                               defaultSelector;
+	private Registry<Consumer<? extends Event<?>>> consumerRegistry;
+	private boolean traceEventPath = false;
+
+	/**
+	 * Configures the component's EventRouter to use the given {code converters}.
+	 *
+	 * @param converters The converters to be used by the event router
+	 * @return {@code this}
+	 */
+	public final SPEC converters(Converter... converters) {
+		this.converter = new DelegatingConverter(converters);
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component's EventRouter to use the given {code converters}.
+	 *
+	 * @param converters The converters to be used by the event router
+	 * @return {@code this}
+	 */
+	public final SPEC converters(List<Converter> converters) {
+		this.converter = new DelegatingConverter(converters);
+		return (SPEC) this;
+	}
+
+
+	/**
+	 * Assigns the component's Filter
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC eventFilter(Filter filter) {
+		Assert.isNull(eventRouter, "Cannot set both a filter and a router. Use one or the other.");
+		this.eventFilter = filter;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Assigns the component's Consumer Invoker
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC consumerInvoker(ConsumerInvoker consumerInvoker) {
+		Assert.isNull(eventRouter, "Cannot set both a consumerInvoker and a router. Use one or the other.");
+		this.consumerInvoker = consumerInvoker;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Assigns the component's EventRouter
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC eventRouter(EventRouter router) {
+		Assert.isNull(eventFilter, "Cannot set both a filter and a router. Use one or the other.");
+		Assert.isNull(consumerInvoker, "Cannot set both a consumerInvoker and a router. Use one or the other.");
+		this.eventRouter = router;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component's EventRouter to broadcast events to all matching
+	 * consumers
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC broadcastEventRouting() {
+		this.eventRoutingStrategy = EventRoutingStrategy.BROADCAST;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component's EventRouter to route events to one consumer that's
+	 * randomly selected from that matching consumers
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC randomEventRouting() {
+		this.eventRoutingStrategy = EventRoutingStrategy.RANDOM;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component's EventRouter to route events to the first of the matching
+	 * consumers
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC firstEventRouting() {
+		this.eventRoutingStrategy = EventRoutingStrategy.FIRST;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component's EventRouter to route events to one consumer selected
+	 * from the matching consumers using a round-robin algorithm
+	 * consumers
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC roundRobinEventRouting() {
+		this.eventRoutingStrategy = EventRoutingStrategy.ROUND_ROBIN;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component's error handler for any errors occurring during dispatch (e.g. Exceptions resulting from
+	 * calling a {@code Consumer#accept} method.
+	 *
+	 * @param dispatchErrorHandler the error handler for dispatching errors
+	 * @return {@code this}
+	 */
+	public SPEC dispatchErrorHandler(Consumer<Throwable> dispatchErrorHandler) {
+		this.dispatchErrorHandler = dispatchErrorHandler;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the component's uncaught error handler for any errors that get reported into this component but
+	 * aren't a
+	 * direct result of dispatching (e.g. errors that originate from another component).
+	 *
+	 * @param uncaughtErrorHandler the error handler for uncaught errors
+	 * @return {@code this}
+	 */
+	public SPEC uncaughtErrorHandler(Consumer<Throwable> uncaughtErrorHandler) {
+		this.uncaughtErrorHandler = uncaughtErrorHandler;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures this component to provide event tracing when dispatching and routing an event.
+	 *
+	 * @return {@code this}
+	 */
+	public final SPEC traceEventPath() {
+		return traceEventPath(true);
+	}
+
+	/**
+	 * Configures this component to provide or not provide event tracing when dispatching and routing an event.
+	 *
+	 * @param b whether to trace the event path or not
+	 * @return {@code this}
+	 */
+	public final SPEC traceEventPath(boolean b) {
+		this.traceEventPath = b;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the {@link reactor.event.registry.Registry} to use when creating this component. Registries can be shared to reduce GC pressure and potentially be persisted across restarts.
+	 *
+	 * @param consumerRegistry the consumer registry to use
+	 * @return {@code this}
+	 */
+	public SPEC consumerRegistry(Registry<Consumer<? extends Event<?>>> consumerRegistry) {
+		this.consumerRegistry = consumerRegistry;
+		return (SPEC) this;
+	}
+
+	/**
+	 * Configures the callback to invoke if a notification key is sent into this component and there are no consumers registered to respond to it.
+	 *
+	 * @param consumerNotFoundHandler the not found handler to use
+	 * @return {@code this}
+	 */
+	public SPEC consumerNotFoundHandler(Consumer<Object> consumerNotFoundHandler) {
+		this.consumerRegistry = new CachingRegistry<Consumer<? extends Event<?>>>(true, true, consumerNotFoundHandler);
+		return (SPEC) this;
+	}
+
+	protected abstract TARGET configure(Reactor reactor, Environment environment);
+
+	@Override
+	protected final TARGET configure(Dispatcher dispatcher, Environment environment) {
+		return configure(createReactor(dispatcher), environment);
+	}
+
+	private Reactor createReactor(Dispatcher dispatcher) {
+		if (traceEventPath) {
+			dispatcher = new TraceableDelegatingDispatcher(dispatcher);
+		}
+		return new Reactor((consumerRegistry != null ? consumerRegistry : createRegistry()),
+		                   dispatcher,
+		                   (eventRouter != null ? eventRouter : createEventRouter()),
+		                   dispatchErrorHandler,
+		                   uncaughtErrorHandler);
+	}
+
+	private EventRouter createEventRouter() {
+		EventRouter evr = new ConsumerFilteringEventRouter(
+				eventFilter != null ? eventFilter : createFilter(),
+				consumerInvoker != null ? consumerInvoker : new ArgumentConvertingConsumerInvoker(converter));
+		if (traceEventPath) {
+			return new TraceableDelegatingEventRouter(evr);
+		} else {
+			return evr;
+		}
+	}
+
+	private Filter createFilter() {
+		Filter filter;
+		if (EventRoutingStrategy.ROUND_ROBIN == eventRoutingStrategy) {
+			filter = new RoundRobinFilter();
+		} else if (EventRoutingStrategy.RANDOM == eventRoutingStrategy) {
+			filter = new RandomFilter();
+		} else if (EventRoutingStrategy.FIRST == eventRoutingStrategy) {
+			filter = new FirstFilter();
+		} else {
+			filter = new PassThroughFilter();
+		}
+		return (traceEventPath ? new TraceableDelegatingFilter(filter) : filter);
+	}
+
+	private Registry createRegistry() {
+		return new CachingRegistry<Consumer<? extends Event<?>>>();
+	}
+
+	protected enum EventRoutingStrategy {
+		BROADCAST, RANDOM, ROUND_ROBIN, FIRST
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/spec/support/package-info.java b/reactor-core/src/main/java/reactor/core/spec/support/package-info.java
new file mode 100644
index 0000000..c99a429
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/spec/support/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Base classes which other spec implementations can inherit from to get a standard set of options.
+ */
+package reactor.core.spec.support;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/core/support/DefaultEnvironmentSupplier.java b/reactor-core/src/main/java/reactor/core/support/DefaultEnvironmentSupplier.java
new file mode 100644
index 0000000..9cf88c3
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/support/DefaultEnvironmentSupplier.java
@@ -0,0 +1,25 @@
+package reactor.core.support;
+
+import reactor.core.Environment;
+import reactor.function.Supplier;
+
+/**
+ * A default implementation of a {@link reactor.function.Supplier} that provides a singleton {@link reactor.core
+ * .Environment} which is created in the constructor.
+ *
+ * @author Jon Brisbin
+ */
+public class DefaultEnvironmentSupplier implements Supplier<Environment> {
+
+	private final Environment env;
+
+	public DefaultEnvironmentSupplier() {
+		this.env = new Environment();
+	}
+
+	@Override
+	public Environment get() {
+		return env;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/core/support/NotifyConsumer.java b/reactor-core/src/main/java/reactor/core/support/NotifyConsumer.java
new file mode 100644
index 0000000..4c8cb10
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/core/support/NotifyConsumer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.support;
+
+import reactor.function.Consumer;
+import reactor.event.Event;
+import reactor.core.Observable;
+import reactor.util.Assert;
+
+/**
+ * A {@code Consumer} that notifies an observable of each value that it has accepted.
+ *
+ * @param <T> the type of the values that the consumer can accept
+ * @author Jon Brisbin
+ */
+public class NotifyConsumer<T> implements Consumer<T> {
+
+	private final Object     notifyKey;
+	private final Observable observable;
+
+	/**
+	 * Creates a new {@code NotifyConsumer} that will notify the given {@code observable} using
+	 * the given {@code notifyKey}. If {@code notifyKey} is {@code null}, {@code observable}
+	 * will be notified without a key.
+	 *
+	 * @param notifyKey  The notification key, may be {@code null}
+	 * @param observable The observable to notify. May not be {@code null}
+	 */
+	public NotifyConsumer(Object notifyKey, Observable observable) {
+		Assert.notNull(observable, "Observable cannot be null.");
+		this.notifyKey = notifyKey;
+		this.observable = observable;
+	}
+
+	@Override
+	public void accept(T t) {
+		Event<T> ev = Event.wrap(t);
+		observable.notify(notifyKey, ev);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/Event.java b/reactor-core/src/main/java/reactor/event/Event.java
new file mode 100644
index 0000000..8db285b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/Event.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event;
+
+import reactor.alloc.Recyclable;
+import reactor.function.Consumer;
+import reactor.tuple.Tuple;
+import reactor.tuple.Tuple2;
+import reactor.util.Assert;
+import reactor.util.UUIDUtils;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * Wrapper for an object that needs to be processed by {@link reactor.function.Consumer}s.
+ *
+ * @param <T>
+ *     The type of the wrapped object
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+public class Event<T> implements Serializable, Recyclable {
+
+  private static final long serialVersionUID = -2476263092040373361L;
+  private final transient Consumer<Throwable> errorConsumer;
+  private volatile        UUID                id;
+  private volatile        Headers             headers;
+  private volatile        Object              replyTo;
+  private volatile        Object              key;
+  private volatile        T                   data;
+
+  /**
+   * Creates a new Event based on the type T of {@data data}
+   *
+   * @param klass
+   */
+  public Event(Class<T> klass) {
+    this.headers = null;
+    this.data = null;
+    this.errorConsumer = null;
+  }
+
+  /**
+   * Creates a new Event with the given {@code headers} and {@code data}.
+   *
+   * @param headers
+   *     The headers
+   * @param data
+   *     The data
+   */
+  public Event(Headers headers, T data) {
+    this.headers = headers;
+    this.data = data;
+    this.errorConsumer = null;
+  }
+
+  /**
+   * Creates a new Event with the given {@code headers}, {@code data} and {@link reactor.function.Consumer<java.lang.Throwable>}.
+   *
+   * @param headers
+   *     The headers
+   * @param data
+   *     The data
+   * @param errorConsumer
+   *     error consumer callback
+   */
+  public Event(Headers headers, T data, Consumer<Throwable> errorConsumer) {
+    this.headers = headers;
+    this.data = data;
+    this.errorConsumer = errorConsumer;
+  }
+
+  /**
+   * Creates a new Event with the given {@code data}. The event will have empty headers.
+   *
+   * @param data
+   *     The data
+   */
+  public Event(T data) {
+    this.data = data;
+    this.errorConsumer = null;
+  }
+
+  /**
+   * Wrap the given object with an {@link Event}.
+   *
+   * @param obj
+   *     The object to wrap.
+   *
+   * @return The new {@link Event}.
+   */
+  public static <T> Event<T> wrap(T obj) {
+    return new Event<T>(obj);
+  }
+
+  /**
+   * Wrap the given object with an {@link Event} and set the {@link Event#getReplyTo() replyTo} to the given {@code
+   * replyToKey}.
+   *
+   * @param obj
+   *     The object to wrap.
+   * @param replyToKey
+   *     The key to use as a {@literal replyTo}.
+   * @param <T>
+   *     The type of the given object.
+   *
+   * @return The new {@link Event}.
+   */
+  public static <T> Event<T> wrap(T obj, Object replyToKey) {
+    return new Event<T>(obj).setReplyTo(replyToKey);
+  }
+
+  /**
+   * Get the globally-unique id of this event.
+   *
+   * @return Unique {@link UUID} of this event.
+   */
+  public synchronized UUID getId() {
+    if (null == id) {
+      id = UUIDUtils.create();
+    }
+    return id;
+  }
+
+  /**
+   * Get the {@link Headers} attached to this event.
+   *
+   * @return The Event's Headers
+   */
+  public synchronized Headers getHeaders() {
+    if (null == headers) {
+      headers = new Headers();
+    }
+    return headers;
+  }
+
+  /**
+   * Get the key to send replies to.
+   *
+   * @return The reply-to key
+   */
+  public Object getReplyTo() {
+    return replyTo;
+  }
+
+  /**
+   * Set the {@code key} that interested parties should send replies to.
+   *
+   * @param replyTo
+   *     The key to use to notify sender of replies.
+   *
+   * @return {@literal this}
+   */
+  public Event<T> setReplyTo(Object replyTo) {
+    Assert.notNull(replyTo, "ReplyTo cannot be null.");
+    this.replyTo = replyTo;
+    return this;
+  }
+
+  /**
+   * Get the key this event was notified on.
+   *
+   * @return The key used to notify consumers of this event.
+   */
+  public Object getKey() {
+    return key;
+  }
+
+  /**
+   * Set the key this event is being notified with.
+   *
+   * @param key
+   *     The key used to notify consumers of this event.
+   *
+   * @return {@literal this}
+   */
+  public Event<T> setKey(Object key) {
+    this.key = key;
+    return this;
+  }
+
+  /**
+   * Get the internal data being wrapped.
+   *
+   * @return The data.
+   */
+  public T getData() {
+    return data;
+  }
+
+  /**
+   * Set the internal data to wrap.
+   *
+   * @param data
+   *     Data to wrap.
+   *
+   * @return {@literal this}
+   */
+  public Event<T> setData(T data) {
+    this.data = data;
+    return this;
+  }
+
+  /**
+   * Get the internal error consumer callback being wrapped.
+   *
+   * @return the consumer.
+   */
+  public Consumer<Throwable> getErrorConsumer() {
+    return errorConsumer;
+  }
+
+  /**
+   * Create a copy of this event, reusing same headers, data and replyTo
+   *
+   * @return {@literal event copy}
+   */
+  public Event<T> copy() {
+    return copy(data);
+  }
+
+  /**
+   * Create a copy of this event, reusing same headers and replyTo
+   *
+   * @return {@literal event copy}
+   */
+  public <E> Event<E> copy(E data) {
+    if (null != replyTo) {
+      return new Event<E>(headers, data, errorConsumer).setReplyTo(replyTo);
+    } else {
+      return new Event<E>(headers, data, errorConsumer);
+    }
+  }
+
+  /**
+   * Consumes error, using a producer defined callback
+   *
+   * @param throwable
+   *     The error to consume
+   */
+  public void consumeError(Throwable throwable) {
+    if (null != errorConsumer) {
+      errorConsumer.accept(throwable);
+    }
+  }
+
+  @Override
+  public void recycle() {
+    this.id = null;
+    if (null != this.headers) {
+      this.headers.headers.clear();
+    }
+    this.replyTo = null;
+    this.key = null;
+    this.data = null;
+  }
+
+  public void override(Event<T> ev) {
+    this.id = ev.id;
+    this.headers = ev.headers;
+    this.replyTo = ev.replyTo;
+    this.data = ev.data;
+  }
+
+  @Override
+  public String toString() {
+    return "Event{" +
+        "id=" + id +
+        ", headers=" + headers +
+        ", replyTo=" + replyTo +
+        ", key=" + key +
+        ", data=" + data +
+        '}';
+  }
+
+  /**
+   * Headers are a Map-like structure of name-value pairs. Header names are case-insensitive, as determined by {@link
+   * String#CASE_INSENSITIVE_ORDER}. A header can be removed by setting its value to {@code null}.
+   */
+  public static class Headers implements Serializable, Iterable<Tuple2<String, Object>> {
+
+    /**
+     * The name of the origin header
+     *
+     * @see #setOrigin(String)
+     * @see #setOrigin(UUID)
+     * @see #getOrigin()
+     */
+    public static final String ORIGIN = "x-reactor-origin";
+
+    private static final long serialVersionUID = 4984692586458514948L;
+
+    private final Object monitor = UUIDUtils.create();
+    private final Map<String, Object> headers;
+
+    private Headers(boolean sealed, Map<String, Object> headers) {
+      Map<String, Object> copy = new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
+      copyHeaders(headers, copy);
+      if (sealed) {
+        this.headers = Collections.unmodifiableMap(copy);
+      } else {
+        this.headers = copy;
+      }
+    }
+
+    /**
+     * Creates a new Headers instance by copying the contents of the given {@code headers} Map. Note that, as the map is
+     * copied, subsequent changes to its contents will have no effect upon the Headers.
+     *
+     * @param headers
+     *     The map to copy.
+     */
+    public Headers(Map<String, Object> headers) {
+      this(false, headers);
+    }
+
+    /**
+     * Create an empty Headers
+     */
+    public Headers() {
+      this(false, null);
+    }
+
+    /**
+     * Sets all of the headers represented by entries in the given {@code headers} Map. Any entry with a null value will
+     * cause the header matching the entry's name to be removed.
+     *
+     * @param headers
+     *     The map of headers to set.
+     *
+     * @return {@code this}
+     */
+    public Headers setAll(Map<String, Object> headers) {
+      if (null == headers || headers.isEmpty()) {
+        return this;
+      } else {
+        synchronized (this.monitor) {
+          copyHeaders(headers, this.headers);
+        }
+      }
+      return this;
+    }
+
+    /**
+     * Set the header value. If {@code value} is {@code null} the header with the given {@code name} will be removed.
+     *
+     * @param name
+     *     The name of the header.
+     * @param value
+     *     The header's value.
+     *
+     * @return {@code this}
+     */
+    public <V> Headers set(String name, V value) {
+      synchronized (this.monitor) {
+        setHeader(name, value, headers);
+      }
+      return this;
+    }
+
+    /**
+     * Set the origin header. The origin is simply a unique id to indicate to consumers where it should send replies. If
+     * {@code id} is {@code null} the origin header will be removed.
+     *
+     * @param id
+     *     The id of the origin component.
+     *
+     * @return {@code this}
+     */
+    public Headers setOrigin(UUID id) {
+      String idString = id == null ? null : id.toString();
+      return setOrigin(idString);
+    }
+
+    /**
+     * Get the origin header
+     *
+     * @return The origin header, may be {@code null}.
+     */
+    public String getOrigin() {
+      synchronized (this.monitor) {
+        return (String) headers.get(ORIGIN);
+      }
+    }
+
+    /**
+     * Set the origin header. The origin is simply a unique id to indicate to consumers where it should send replies. If
+     * {@code id} is {@code null} this origin header will be removed.
+     *
+     * @param id
+     *     The id of the origin component.
+     *
+     * @return {@code this}
+     */
+    public Headers setOrigin(String id) {
+      synchronized (this.monitor) {
+        setHeader(ORIGIN, id, headers);
+      }
+      return this;
+    }
+
+    /**
+     * Get the value for the given header.
+     *
+     * @param name
+     *     The header name.
+     *
+     * @return The value of the header, or {@code null} if none exists.
+     */
+    @SuppressWarnings("unchecked")
+    public <V> V get(String name) {
+      synchronized (monitor) {
+        return (V) headers.get(name);
+      }
+    }
+
+    /**
+     * Determine whether the headers contain a value for the given name.
+     *
+     * @param name
+     *     The header name.
+     *
+     * @return {@code true} if a value exists, {@code false} otherwise.
+     */
+    public boolean contains(String name) {
+      synchronized (monitor) {
+        return headers.containsKey(name);
+      }
+    }
+
+    /**
+     * Get these headers as an unmodifiable {@link Map}.
+     *
+     * @return The unmodifiable header map
+     */
+    public Map<String, Object> asMap() {
+      synchronized (monitor) {
+        return Collections.unmodifiableMap(headers);
+      }
+    }
+
+    /**
+     * Get the headers as a read-only version
+     *
+     * @return A read-only version of the headers.
+     */
+    public Headers readOnly() {
+      synchronized (monitor) {
+        return new Headers(true, headers);
+      }
+    }
+
+    /**
+     * Returns an unmodifiable Iterator over a copy of this Headers' contents.
+     */
+    @Override
+    public Iterator<Tuple2<String, Object>> iterator() {
+      synchronized (this.monitor) {
+        List<Tuple2<String, Object>> headers = new ArrayList<Tuple2<String, Object>>(this.headers.size());
+        for (Map.Entry<String, Object> header : this.headers.entrySet()) {
+          headers.add(Tuple.of(header.getKey(), header.getValue()));
+        }
+        return Collections.unmodifiableList(headers).iterator();
+      }
+    }
+
+    @Override
+    public String toString() {
+      return headers.toString();
+    }
+
+    private void copyHeaders(Map<String, Object> source, Map<String, Object> target) {
+      if (source != null) {
+        for (Map.Entry<String, Object> entry : source.entrySet()) {
+          setHeader(entry.getKey(), entry.getValue(), target);
+        }
+      }
+    }
+
+    private void setHeader(String name, Object value, Map<String, Object> target) {
+      if (value == null) {
+        target.remove(name);
+      } else {
+        target.put(name, value);
+      }
+    }
+  }
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/alloc/EventAllocator.java b/reactor-core/src/main/java/reactor/event/alloc/EventAllocator.java
new file mode 100644
index 0000000..50b5943
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/alloc/EventAllocator.java
@@ -0,0 +1,84 @@
+package reactor.event.alloc;
+
+import reactor.alloc.Allocator;
+import reactor.alloc.Reference;
+import reactor.alloc.ReferenceCountingAllocator;
+import reactor.alloc.factory.EventFactorySupplier;
+import reactor.event.Event;
+
+import java.util.HashMap;
+
+/**
+ * Generic Event Allocator. Allocates events based on their generic type.
+ *
+ * @author Oleksandr Petrov
+ */
+public abstract class EventAllocator {
+
+	private final Object                    monitor;
+	private final HashMap<Class, Allocator> eventPools;
+
+	public EventAllocator() {
+		this(new Class[0]);
+	}
+
+	/**
+	 * Create a new {@link EventAllocator}, containing pre-created
+	 * {@link reactor.alloc.Allocator}s for given {@data class}es.
+	 *
+	 * @param classes
+	 */
+	@SuppressWarnings("unchecked")
+	public EventAllocator(Class[] classes) {
+		this.eventPools = new HashMap<Class, Allocator>();
+		this.monitor = new Object();
+		for (Class c : classes) {
+			eventPools.put(c, makeAllocator(c));
+    }
+  }
+
+  /**
+   * Allocate an object from the internal pool, based on the type of Event.
+   *
+   * @param klass generic type of {@link reactor.event.Event}
+   * @param <T> generic type of {@link reactor.event.Event}
+   *
+   * @return a {@link reactor.alloc.Reference} that can be retained and released.
+   */
+  @SuppressWarnings("unchecked")
+  public <T> Reference<Event<T>> get(Class<T> klass) {
+    if(!eventPools.containsKey(klass)) {
+      synchronized (monitor) {
+        // Check once again if another thread didn't create a supplier in a meanwhile
+        if(!eventPools.containsKey(klass)) {
+          eventPools.put(klass, makeAllocator(klass));
+        }
+      }
+    }
+    return eventPools.get(klass).allocate();
+  }
+
+  /**
+   * Make a new allocator for {@link reactor.event.Event}s with generic type of {@data klass}
+   *
+   * @param klass generic type of {@link reactor.event.Event}
+   * @param <T> generic type of {@link reactor.event.Event}
+   */
+  protected abstract <T> Allocator<Event<T>> makeAllocator(Class<T> klass);
+
+  /**
+   * Default Event Allocator, uses {@link reactor.alloc.ReferenceCountingAllocator} for
+   * allocating and recycling events.
+   *
+   * @return newly constructed event alloator.
+   */
+  public static EventAllocator defaultEventAllocator() {
+    return new EventAllocator() {
+      @SuppressWarnings("unchecked")
+      @Override
+      protected <T> Allocator<Event<T>> makeAllocator(Class<T> klass) {
+        return new ReferenceCountingAllocator<Event<T>>(new EventFactorySupplier(klass));
+      }
+    };
+  }
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/AbstractLifecycleDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/AbstractLifecycleDispatcher.java
new file mode 100644
index 0000000..3ec8524
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/AbstractLifecycleDispatcher.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.dispatch;
+
+import reactor.alloc.Recyclable;
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+import reactor.event.registry.Registry;
+import reactor.event.routing.EventRouter;
+import reactor.function.Consumer;
+import reactor.util.Assert;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A {@code Dispatcher} that has a lifecycle.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public abstract class AbstractLifecycleDispatcher implements Dispatcher {
+
+	private static final EventRouter COMPLETION_CONSUMER_EVENT_ROUTER = new EventRouter() {
+		@Override
+		public void route(Object key,
+		                  Event<?> event,
+		                  List<Registration<? extends Consumer<? extends Event<?>>>> consumers,
+		                  Consumer<?> completionConsumer,
+		                  Consumer<Throwable> errorConsumer) {
+			completionConsumer.accept(null);
+		}
+	};
+
+	private final AtomicBoolean alive   = new AtomicBoolean(true);
+	private final ClassLoader   context = new ClassLoader(Thread.currentThread()
+	                                                            .getContextClassLoader()) {
+	};
+
+	protected AbstractLifecycleDispatcher() {
+		super();
+	}
+
+	protected static void route(Task task) {
+		if (null == task.eventRouter) {
+			return;
+		}
+		try {
+			task.eventRouter.route(
+					task.key,
+					task.event,
+					(null != task.consumerRegistry ? task.consumerRegistry.select(task.key) : null),
+					task.completionConsumer,
+					task.errorConsumer
+			);
+		} finally {
+			task.recycle();
+		}
+	}
+
+	@Override
+	public boolean alive() {
+		return alive.get();
+	}
+
+	@Override
+	public boolean awaitAndShutdown() {
+		return awaitAndShutdown(Integer.MAX_VALUE, TimeUnit.SECONDS);
+	}
+
+	@Override
+	public void shutdown() {
+		alive.compareAndSet(true, false);
+	}
+
+	@Override
+	public void halt() {
+		alive.compareAndSet(true, false);
+	}
+
+	/**
+	 * Dispatchers can be traced through a {@code contextClassLoader} to let producers adapting their dispatching
+	 * strategy
+	 *
+	 * @return boolean true if the programs is already run by this dispatcher
+	 */
+	protected final boolean isInContext() {
+		return context == Thread.currentThread().getContextClassLoader();
+	}
+
+	protected final ClassLoader getContext() {
+		return context;
+	}
+
+	@Override
+	public final <E extends Event<?>> void dispatch(E event,
+	                                                EventRouter eventRouter,
+	                                                Consumer<E> consumer,
+	                                                Consumer<Throwable> errorConsumer) {
+		dispatch(null, event, null, errorConsumer, eventRouter, consumer);
+	}
+
+	@Override
+	public <E extends Event<?>> void dispatch(Object key,
+	                                          E event,
+	                                          Registry<Consumer<? extends Event<?>>> consumerRegistry,
+	                                          Consumer<Throwable> errorConsumer,
+	                                          EventRouter eventRouter,
+	                                          Consumer<E> completionConsumer) {
+		Assert.isTrue(alive(), "This Dispatcher has been shut down.");
+
+		try {
+			Task task;
+			boolean isInContext = isInContext();
+			if (isInContext) {
+				task = allocateRecursiveTask();
+			} else {
+				task = allocateTask();
+			}
+
+			task.setKey(key)
+			    .setEvent(event)
+			    .setConsumerRegistry(consumerRegistry)
+			    .setErrorConsumer(errorConsumer)
+			    .setEventRouter(eventRouter)
+			    .setCompletionConsumer(completionConsumer);
+
+			if (isInContext) {
+				addToTailRecursionPile(task);
+			} else {
+				execute(task);
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+			throw new IllegalStateException(e.getMessage(), e);
+		}
+	}
+
+	@Override
+	public void execute(final Runnable command) {
+		dispatch(null, COMPLETION_CONSUMER_EVENT_ROUTER, new Consumer<Event<?>>() {
+			@Override
+			public void accept(Event<?> ev) {
+				command.run();
+			}
+		}, null);
+	}
+
+	protected void addToTailRecursionPile(Task task) {
+	}
+
+	protected abstract Task allocateRecursiveTask();
+
+	protected abstract Task allocateTask();
+
+	protected abstract void execute(Task task);
+
+	public abstract class Task implements Runnable, Recyclable {
+
+		protected volatile Object                                 key;
+		protected volatile Registry<Consumer<? extends Event<?>>> consumerRegistry;
+		protected volatile Event<?>                               event;
+		protected volatile Consumer<?>                            completionConsumer;
+		protected volatile Consumer<Throwable>                    errorConsumer;
+		protected volatile EventRouter                            eventRouter;
+
+		public Task setKey(Object key) {
+			this.key = key;
+			return this;
+		}
+
+		public Task setConsumerRegistry(Registry<Consumer<? extends Event<?>>> consumerRegistry) {
+			this.consumerRegistry = consumerRegistry;
+			return this;
+		}
+
+		public Task setEvent(Event<?> event) {
+			this.event = event;
+			return this;
+		}
+
+		public Task setCompletionConsumer(Consumer<?> completionConsumer) {
+			this.completionConsumer = completionConsumer;
+			return this;
+		}
+
+		public Task setErrorConsumer(Consumer<Throwable> errorConsumer) {
+			this.errorConsumer = errorConsumer;
+			return this;
+		}
+
+		public Task setEventRouter(EventRouter eventRouter) {
+			this.eventRouter = eventRouter;
+			return this;
+		}
+
+		@Override
+		public void recycle() {
+			key = null;
+			consumerRegistry = null;
+			event = null;
+			completionConsumer = null;
+			errorConsumer = null;
+			eventRouter = null;
+		}
+
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/AbstractMultiThreadDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/AbstractMultiThreadDispatcher.java
new file mode 100644
index 0000000..62ae70c
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/AbstractMultiThreadDispatcher.java
@@ -0,0 +1,76 @@
+package reactor.event.dispatch;
+
+import reactor.alloc.factory.BatchFactorySupplier;
+import reactor.function.Supplier;
+import reactor.queue.BlockingQueueFactory;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+
+/**
+ * Base implementation for multi-threaded dispatchers
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public abstract class AbstractMultiThreadDispatcher extends AbstractLifecycleDispatcher {
+
+	private static final AtomicLongFieldUpdater<AbstractMultiThreadDispatcher> MPSC_ROUTER
+			= AtomicLongFieldUpdater.newUpdater(AbstractMultiThreadDispatcher.class, "recursiveTaskRouter");
+
+	private volatile long recursiveTaskRouter = 0L;
+
+	private final int                                   backlog;
+	private final int                                   numberThreads;
+	private final BlockingQueue<Task>                   recursiveTasks;
+	private final BatchFactorySupplier<MultiThreadTask> taskFactory;
+
+	protected AbstractMultiThreadDispatcher(int numberThreads, int backlog) {
+		this.backlog = backlog;
+		this.numberThreads = numberThreads;
+		this.taskFactory = new BatchFactorySupplier<MultiThreadTask>(
+				backlog,
+				new Supplier<MultiThreadTask>() {
+					@Override
+					public MultiThreadTask get() {
+						return new MultiThreadTask();
+					}
+				}
+		);
+		this.recursiveTasks = BlockingQueueFactory.createQueue();
+	}
+
+	public int getBacklog() {
+		return backlog;
+	}
+
+	@Override
+	protected Task allocateRecursiveTask() {
+		return taskFactory.get();
+	}
+
+	@Override
+	protected void addToTailRecursionPile(Task task) {
+		recursiveTasks.add(task);
+	}
+
+	protected Task allocateTask() {
+		return taskFactory.get();
+	}
+
+	protected class MultiThreadTask extends Task {
+		@Override
+		public void run() {
+			route(this);
+			if (MPSC_ROUTER.compareAndSet(AbstractMultiThreadDispatcher.this, 0L, Thread.currentThread().getId())) {
+				Task t;
+				while (null != (t = recursiveTasks.poll())) {
+					route(t);
+				}
+				recursiveTaskRouter = 0L;
+			}
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/AbstractSingleThreadDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/AbstractSingleThreadDispatcher.java
new file mode 100644
index 0000000..4222acb
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/AbstractSingleThreadDispatcher.java
@@ -0,0 +1,72 @@
+package reactor.event.dispatch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base Implementation for single-threaded Dispatchers.
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public abstract class AbstractSingleThreadDispatcher extends AbstractLifecycleDispatcher {
+
+	private final List<Task> tailRecursionPile = new ArrayList<Task>();
+	private final int backlog;
+
+	private volatile int tailRecurseSeq        = -1;
+	private volatile int tailRecursionPileSize = 0;
+
+	public AbstractSingleThreadDispatcher(int backlog) {
+		this.backlog = backlog;
+		expandTailRecursionPile(backlog);
+	}
+
+	public int getBacklog() {
+		return backlog;
+	}
+
+	protected void expandTailRecursionPile(int amount) {
+		for(int i = 0; i < amount; i++) {
+			tailRecursionPile.add(new SingleThreadTask());
+		}
+		this.tailRecursionPileSize = tailRecursionPile.size();
+	}
+
+	protected Task allocateRecursiveTask() {
+		int next = ++tailRecurseSeq;
+		if(next == tailRecursionPileSize) {
+			expandTailRecursionPile(backlog);
+		}
+		return tailRecursionPile.get(next);
+	}
+
+	protected abstract Task allocateTask();
+
+	protected class SingleThreadTask extends Task {
+		@Override
+		public void run() {
+			route(this);
+
+			//Process any recursive tasks
+			if(tailRecurseSeq < 0) {
+				return;
+			}
+			Task task;
+			int next = 0;
+			while(next <= tailRecurseSeq) {
+				task = tailRecursionPile.get(next++);
+				route(task);
+			}
+			// clean up extra tasks
+			next = tailRecurseSeq;
+			while(next > backlog) {
+				tailRecursionPile.remove(next--);
+			}
+			tailRecurseSeq = -1;
+		}
+	}
+
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/ActorDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/ActorDispatcher.java
new file mode 100644
index 0000000..fbc200d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/ActorDispatcher.java
@@ -0,0 +1,118 @@
+package reactor.event.dispatch;
+
+import reactor.event.Event;
+import reactor.event.registry.Registry;
+import reactor.event.routing.EventRouter;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.util.Assert;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An implementation of {@link Dispatcher} that maps a key to a delegate dispatcher and caches the mapping within
+ * its internal {@link Registry<Dispatcher>}. Thus making similar key-based dispatching reusing the same dispatcher,
+ * a pattern also dubbed as "Actor".
+ *
+ * @author Stephane Maldini
+ */
+public final class ActorDispatcher implements Dispatcher {
+
+	private final Function<Object, Dispatcher> delegateMapper;
+	private final Map<Integer, Dispatcher> dispatcherCache = new ConcurrentHashMap<Integer, Dispatcher>();
+	private final int                      emptyHashcode   = this.hashCode();
+
+	public ActorDispatcher(Function<Object, Dispatcher> delegate) {
+		Assert.notNull(delegate, "Delegate Dispatcher Supplier cannot be null.");
+		this.delegateMapper = delegate;
+	}
+
+	@Override
+	public boolean alive() {
+		boolean alive = true;
+		for(Dispatcher dispatcher : new HashSet<Dispatcher>(dispatcherCache.values())) {
+			alive &= dispatcher.alive();
+			if(!alive) break;
+		}
+		return alive;
+	}
+
+	@Override
+	public boolean awaitAndShutdown() {
+		return awaitAndShutdown(Integer.MAX_VALUE, TimeUnit.SECONDS);
+	}
+
+	@Override
+	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
+		boolean alive = true;
+		for(Dispatcher dispatcher : new HashSet<Dispatcher>(dispatcherCache.values())) {
+			if(dispatcher.alive()) {
+				alive &= dispatcher.awaitAndShutdown(timeout, timeUnit);
+			}
+			if(!alive) break;
+		}
+		return alive;
+	}
+
+	@Override
+	public void shutdown() {
+		for(Dispatcher dispatcher : new HashSet<Dispatcher>(dispatcherCache.values())) {
+			dispatcher.shutdown();
+		}
+	}
+
+	@Override
+	public void halt() {
+		for(Dispatcher dispatcher : new HashSet<Dispatcher>(dispatcherCache.values())) {
+			dispatcher.halt();
+		}
+	}
+
+	@Override
+	public <E extends Event<?>> void dispatch(Object key,
+	                                          E event,
+	                                          Registry<Consumer<? extends Event<?>>> consumerRegistry,
+	                                          Consumer<Throwable> errorConsumer,
+	                                          EventRouter eventRouter,
+	                                          Consumer<E> completionConsumer) {
+
+		int hashCode = key == null ? emptyHashcode : key.hashCode();
+		Dispatcher delegate = dispatcherCache.get(hashCode);
+		if(delegate == null) {
+			delegate = delegateMapper.apply(key);
+			dispatcherCache.put(hashCode, delegate);
+		}
+
+		delegate.dispatch(
+				key,
+				event,
+				consumerRegistry,
+				errorConsumer,
+				eventRouter,
+				completionConsumer);
+	}
+
+	@Override
+	public <E extends Event<?>> void dispatch(E event,
+	                                          EventRouter eventRouter,
+	                                          Consumer<E> consumer,
+	                                          Consumer<Throwable> errorConsumer) {
+		dispatch(null, event, null, errorConsumer, eventRouter, consumer);
+	}
+
+	@Override
+	public void execute(Runnable command) {
+		int hashCode = command.hashCode();
+		Dispatcher delegate = dispatcherCache.get(hashCode);
+		if(delegate == null) {
+			delegate = delegateMapper.apply(command);
+			dispatcherCache.put(hashCode, delegate);
+		}
+
+		delegate.execute(command);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/Dispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/Dispatcher.java
new file mode 100644
index 0000000..6b4536b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/Dispatcher.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.dispatch;
+
+import reactor.event.Event;
+import reactor.event.registry.Registry;
+import reactor.event.routing.EventRouter;
+import reactor.function.Consumer;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@code Dispatcher} is used to {@link Dispatcher#dispatch(Object, Event, Registry, Consumer, EventRouter, Consumer)
+ * dispatch} {@link Event}s to {@link Consumer}s. The details of how the dispatching is performed, for example on the
+ * same thread or using a different thread, are determined by the implementation.
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ */
+public interface Dispatcher extends Executor {
+
+	/**
+	 * Determine whether this {@code Dispatcher} can be used for {@link Dispatcher#dispatch(Object, Event, Registry,
+	 * Consumer, EventRouter, Consumer) dispatching}.
+	 *
+	 * @return {@literal true} if this {@code Dispatcher} is alive and can be used, {@literal false} otherwise.
+	 */
+	boolean alive();
+
+	/**
+	 * Block until all submitted tasks have completed, then do a normal {@link #shutdown()}.
+	 */
+	boolean awaitAndShutdown();
+
+	/**
+	 * Block until all submitted tasks have completed, then do a normal {@link #shutdown()}.
+	 */
+	boolean awaitAndShutdown(long timeout, TimeUnit timeUnit);
+
+	/**
+	 * Shutdown this {@code Dispatcher} such that it can no longer be used.
+	 */
+	void shutdown();
+
+	/**
+	 * Shutdown this {@code Dispatcher}, forcibly halting any tasks currently executing and discarding any tasks that have
+	 * not yet been exected.
+	 */
+	void halt();
+
+	/**
+	 * Instruct the {@code Dispatcher} to dispatch the {@code event} that has the given {@code key}. The {@link Consumer}s
+	 * that will receive the event are selected from the {@code consumerRegistry}, and the event is routed to them using
+	 * the {@code eventRouter}. In the event of an error during dispatching, the {@code errorConsumer} will be called. In
+	 * the event of successful dispatching, the {@code completionConsumer} will be called.
+	 *
+	 * @param key                The key associated with the event
+	 * @param event              The event
+	 * @param consumerRegistry   The registry from which consumer's are selected
+	 * @param errorConsumer      The consumer that is invoked if dispatch fails. May be {@code null}
+	 * @param eventRouter        Used to route the event to the selected consumers
+	 * @param completionConsumer The consumer that is driven if dispatch succeeds May be {@code null}
+	 * @param <E>                type of the event
+	 * @throws IllegalStateException If the {@code Dispatcher} is not {@link Dispatcher#alive() alive}
+	 */
+	<E extends Event<?>> void dispatch(Object key,
+																		 E event,
+																		 Registry<Consumer<? extends Event<?>>> consumerRegistry,
+																		 Consumer<Throwable> errorConsumer,
+																		 EventRouter eventRouter,
+																		 Consumer<E> completionConsumer);
+
+	/**
+	 * Instruct the {@code Dispatcher} to dispatch the given {@code Event} using the given {@link Consumer}. This optimized
+	 * route bypasses all selection and routing so provides a significant throughput boost. If an error occurs, the given
+	 * {@code errorConsumer} will be invoked.
+	 *
+	 * @param event         the event
+	 * @param eventRouter   invokes the {@code Consumer} in the correct thread
+	 * @param errorConsumer consumer to invoke if dispatch fails (may be {@code null})
+	 * @param <E>           type of the event
+	 * @throws IllegalStateException If the {@code Dispatcher} is not {@link Dispatcher#alive() alive}
+	 */
+	<E extends Event<?>> void dispatch(E event,
+																		 EventRouter eventRouter,
+																		 Consumer<E> consumer,
+																		 Consumer<Throwable> errorConsumer);
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/EventLoopDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/EventLoopDispatcher.java
new file mode 100644
index 0000000..0a3a911
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/EventLoopDispatcher.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.dispatch;
+
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.dsl.ProducerType;
+import reactor.function.Consumer;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Implementation of {@link Dispatcher} that uses a {@link BlockingQueue} to queue tasks to be executed.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+ at Deprecated
+public final class EventLoopDispatcher extends RingBufferDispatcher {
+
+	/**
+	 * Creates a new {@literal EventLoopDispatcher} with the given {@literal name} and {@literal backlog}.
+	 *
+	 * @param name
+	 * 		The name
+	 * @param backlog
+	 * 		The backlog size
+	 */
+	public EventLoopDispatcher(String name, int backlog) {
+		this(name, backlog, null);
+	}
+
+	/**
+	 * Creates a new {@literal EventLoopDispatcher} with the given {@literal name} and {@literal backlog}.
+	 *
+	 * @param name
+	 * 		The name
+	 * @param backlog
+	 * 		The backlog size
+	 * @param uncaughtExceptionHandler
+	 * 		The {@code UncaughtExceptionHandler}
+	 */
+	public EventLoopDispatcher(final String name,
+	                           int backlog,
+	                           final Consumer<Throwable> uncaughtExceptionHandler) {
+		super(name, backlog, uncaughtExceptionHandler, ProducerType.MULTI, new BlockingWaitStrategy());
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/ParkWaitStrategy.java b/reactor-core/src/main/java/reactor/event/dispatch/ParkWaitStrategy.java
new file mode 100644
index 0000000..75b350f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/ParkWaitStrategy.java
@@ -0,0 +1,41 @@
+package reactor.event.dispatch;
+
+import com.lmax.disruptor.*;
+
+import java.util.concurrent.locks.LockSupport;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ParkWaitStrategy implements WaitStrategy {
+
+	private final long parkFor;
+
+	public ParkWaitStrategy() {
+		this(250);
+	}
+
+	public ParkWaitStrategy(long parkFor) {
+		this.parkFor = parkFor;
+	}
+
+	@Override
+	public long waitFor(long sequence,
+	                    Sequence cursor,
+	                    Sequence dependentSequence,
+	                    SequenceBarrier barrier) throws AlertException,
+	                                                    InterruptedException,
+	                                                    TimeoutException {
+		long availableSequence;
+		while ((availableSequence = dependentSequence.get()) < sequence) {
+			barrier.checkAlert();
+			LockSupport.parkNanos(parkFor);
+		}
+		return availableSequence;
+	}
+
+	@Override
+	public void signalAllWhenBlocking() {
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/RingBufferDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/RingBufferDispatcher.java
new file mode 100644
index 0000000..757096f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/RingBufferDispatcher.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.dispatch;
+
+import com.lmax.disruptor.*;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.function.Consumer;
+import reactor.support.NamedDaemonThreadFactory;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of a {@link Dispatcher} that uses a {@link RingBuffer} to queue tasks to execute.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class RingBufferDispatcher extends AbstractSingleThreadDispatcher {
+
+	private static final int DEFAULT_BUFFER_SIZE = 1024;
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+	private final ExecutorService            executor;
+	private final Disruptor<RingBufferTask>  disruptor;
+	private final RingBuffer<RingBufferTask> ringBuffer;
+
+	/**
+	 * Creates a new {@code RingBufferDispatcher} with the given {@code name}. It will use a RingBuffer with 1024 slots,
+	 * configured with a producer type of {@link ProducerType#MULTI MULTI} and a {@link BlockingWaitStrategy blocking wait
+	 * strategy}.
+	 *
+	 * @param name
+	 * 		The name of the dispatcher.
+	 */
+	public RingBufferDispatcher(String name) {
+		this(name, DEFAULT_BUFFER_SIZE, null, ProducerType.MULTI, new BlockingWaitStrategy());
+	}
+
+	/**
+	 * Creates a new {@literal RingBufferDispatcher} with the given {@code name}. It will use a {@link RingBuffer} with
+	 * {@code bufferSize} slots, configured with the given {@code producerType} and {@code waitStrategy}.
+	 *
+	 * @param name
+	 * 		The name of the dispatcher
+	 * @param bufferSize
+	 * 		The size to configure the ring buffer with
+	 * @param producerType
+	 * 		The producer type to configure the ring buffer with
+	 * @param waitStrategy
+	 * 		The wait strategy to configure the ring buffer with
+	 */
+	@SuppressWarnings({"unchecked"})
+	public RingBufferDispatcher(String name,
+	                            int bufferSize,
+	                            final Consumer<Throwable> uncaughtExceptionHandler,
+	                            ProducerType producerType,
+	                            WaitStrategy waitStrategy) {
+		super(bufferSize);
+		this.executor = Executors.newSingleThreadExecutor(new NamedDaemonThreadFactory(name, getContext()));
+		this.disruptor = new Disruptor<RingBufferTask>(
+				new EventFactory<RingBufferTask>() {
+					@Override
+					public RingBufferTask newInstance() {
+						return new RingBufferTask();
+					}
+				},
+				bufferSize,
+				executor,
+				producerType,
+				waitStrategy
+		);
+
+		this.disruptor.handleExceptionsWith(new ExceptionHandler() {
+			@Override
+			public void handleEventException(Throwable ex, long sequence, Object event) {
+				handleOnStartException(ex);
+			}
+
+			@Override
+			public void handleOnStartException(Throwable ex) {
+				if (null != uncaughtExceptionHandler) {
+					uncaughtExceptionHandler.accept(ex);
+				} else {
+					log.error(ex.getMessage(), ex);
+				}
+			}
+
+			@Override
+			public void handleOnShutdownException(Throwable ex) {
+				handleOnStartException(ex);
+			}
+		});
+		this.disruptor.handleEventsWith(new EventHandler<RingBufferTask>() {
+			@Override
+			public void onEvent(RingBufferTask task, long sequence, boolean endOfBatch) throws Exception {
+				task.run();
+			}
+		});
+
+		this.ringBuffer = disruptor.start();
+	}
+
+	@Override
+	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
+		shutdown();
+		try {
+			executor.awaitTermination(timeout, timeUnit);
+			disruptor.shutdown();
+		} catch (InterruptedException e) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public void shutdown() {
+		executor.shutdown();
+		disruptor.shutdown();
+		super.shutdown();
+	}
+
+	@Override
+	public void halt() {
+		executor.shutdownNow();
+		disruptor.halt();
+		super.halt();
+	}
+
+	@Override
+	protected Task allocateTask() {
+		long seqId = ringBuffer.next();
+		return ringBuffer.get(seqId).setSequenceId(seqId);
+	}
+
+	protected void execute(Task task) {
+		ringBuffer.publish(((RingBufferTask) task).getSequenceId());
+	}
+
+	private class RingBufferTask extends SingleThreadTask {
+		private long sequenceId;
+
+		public long getSequenceId() {
+			return sequenceId;
+		}
+
+		public RingBufferTask setSequenceId(long sequenceId) {
+			this.sequenceId = sequenceId;
+			return this;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/SynchronousDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/SynchronousDispatcher.java
new file mode 100644
index 0000000..97350a1
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/SynchronousDispatcher.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.dispatch;
+
+import reactor.event.Event;
+import reactor.event.registry.Registry;
+import reactor.event.routing.EventRouter;
+import reactor.function.Consumer;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link Dispatcher} implementation that dispatches events using the calling thread.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public final class SynchronousDispatcher implements Dispatcher {
+
+	public SynchronousDispatcher() {
+	}
+
+	@Override
+	public boolean alive() {
+		return true;
+	}
+
+	@Override
+	public boolean awaitAndShutdown() {
+		return true;
+	}
+
+	@Override
+	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
+		return true;
+	}
+
+	@Override
+	public void shutdown() {
+	}
+
+	@Override
+	public void halt() {
+	}
+
+	@Override
+	public <E extends Event<?>> void dispatch(E event,
+	                                          EventRouter eventRouter,
+	                                          Consumer<E> consumer,
+	                                          Consumer<Throwable> errorConsumer) {
+		dispatch(null, event, null, errorConsumer, eventRouter, consumer);
+	}
+
+	@Override
+	public <E extends Event<?>> void dispatch(Object key,
+	                                          E event,
+	                                          Registry<Consumer<? extends Event<?>>> consumerRegistry,
+	                                          Consumer<Throwable> errorConsumer,
+	                                          EventRouter eventRouter,
+	                                          Consumer<E> completionConsumer) {
+		eventRouter.route(key,
+		                  event,
+		                  (null != consumerRegistry ? consumerRegistry.select(key) : null),
+		                  completionConsumer,
+		                  errorConsumer);
+	}
+
+	@Override
+	public void execute(Runnable command) {
+		command.run();
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/ThreadPoolExecutorDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/ThreadPoolExecutorDispatcher.java
new file mode 100644
index 0000000..b0efbb1
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/ThreadPoolExecutorDispatcher.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.dispatch;
+
+import reactor.support.NamedDaemonThreadFactory;
+
+import java.util.concurrent.*;
+
+/**
+ * A {@code Dispatcher} that uses a {@link ThreadPoolExecutor} with an unbounded queue to dispatch events.
+ *
+ * @author Andy Wilkinson
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class ThreadPoolExecutorDispatcher extends AbstractMultiThreadDispatcher {
+
+	private final ExecutorService executor;
+
+	/**
+	 * Creates a new {@literal ThreadPoolExecutorDispatcher} with the given {@literal poolSize} and {@literal backlog}.
+	 * By default, a {@link java.util.concurrent.RejectedExecutionHandler} is created which runs the submitted {@code
+	 * Runnable} in the calling thread. To change this behavior, specify your own.
+	 *
+	 * @param poolSize
+	 * 		the pool size
+	 * @param backlog
+	 * 		the backlog size
+	 */
+	public ThreadPoolExecutorDispatcher(int poolSize, int backlog) {
+		this(poolSize, backlog, "threadPoolExecutorDispatcher");
+	}
+
+	/**
+	 * Create a new {@literal ThreadPoolExecutorDispatcher} with the given size, backlog, name, and {@link
+	 * java.util.concurrent.RejectedExecutionHandler}.
+	 *
+	 * @param poolSize
+	 * 		the pool size
+	 * @param backlog
+	 * 		the backlog size
+	 * @param threadName
+	 * 		the name prefix to use when creating threads
+	 */
+	public ThreadPoolExecutorDispatcher(int poolSize,
+	                                    int backlog,
+	                                    String threadName) {
+		this(poolSize,
+		     backlog,
+		     threadName,
+		     new LinkedBlockingQueue<Runnable>(backlog),
+		     new RejectedExecutionHandler() {
+			     @Override
+			     public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+				     r.run();
+			     }
+		     });
+	}
+
+	/**
+	 * Create a new {@literal ThreadPoolExecutorDispatcher} with the given size, backlog, name, and {@link
+	 * java.util.concurrent.RejectedExecutionHandler}.
+	 *
+	 * @param poolSize
+	 * 		the pool size
+	 * @param backlog
+	 * 		the backlog size
+	 * @param threadName
+	 * 		the name prefix to use when creating threads
+	 * @param rejectedExecutionHandler
+	 * 		the {@code RejectedExecutionHandler} to use when jobs can't be submitted to the thread pool
+	 */
+	public ThreadPoolExecutorDispatcher(int poolSize,
+	                                    int backlog,
+	                                    String threadName,
+	                                    BlockingQueue<Runnable> workQueue,
+	                                    RejectedExecutionHandler rejectedExecutionHandler) {
+		super(poolSize, backlog);
+		this.executor = new ThreadPoolExecutor(
+				poolSize,
+				poolSize,
+				0L,
+				TimeUnit.MILLISECONDS,
+				workQueue,
+				new NamedDaemonThreadFactory(threadName, getContext()),
+				rejectedExecutionHandler
+		);
+	}
+
+	/**
+	 * Create a new {@literal ThreadPoolTaskExecutor} with the given backlog and {@link
+	 * java.util.concurrent.ExecutorService}.
+	 *
+	 * @param backlog
+	 * 		the task backlog
+	 * @param poolSize
+	 * 		the number of threads
+	 * @param executor
+	 * 		the executor to use to execute tasks
+	 */
+	public ThreadPoolExecutorDispatcher(int backlog, int poolSize, ExecutorService executor) {
+		super(poolSize, backlog);
+		this.executor = executor;
+	}
+
+	@Override
+	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
+		shutdown();
+		try {
+			if(!executor.awaitTermination(timeout, timeUnit)) {
+				return false;
+			}
+		} catch(InterruptedException e) {
+			Thread.currentThread().interrupt();
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public void shutdown() {
+		executor.shutdown();
+		super.shutdown();
+	}
+
+	@Override
+	public void halt() {
+		executor.shutdownNow();
+		super.halt();
+	}
+
+	@Override
+	protected void execute(Task task) {
+		executor.execute(task);
+	}
+
+	@Override
+	public void execute(Runnable command) {
+		executor.execute(command);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/TraceableDelegatingDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/TraceableDelegatingDispatcher.java
new file mode 100644
index 0000000..aa9b7ed
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/TraceableDelegatingDispatcher.java
@@ -0,0 +1,101 @@
+package reactor.event.dispatch;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.event.Event;
+import reactor.event.registry.Registry;
+import reactor.event.routing.EventRouter;
+import reactor.function.Consumer;
+import reactor.util.Assert;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An implementation of {@link reactor.event.dispatch.Dispatcher} that traces activity through it.
+ *
+ * @author Jon Brisbin
+ */
+public class TraceableDelegatingDispatcher implements Dispatcher {
+
+	private final Dispatcher delegate;
+	private final Logger     log;
+
+	public TraceableDelegatingDispatcher(Dispatcher delegate) {
+		Assert.notNull(delegate, "Delegate Dispatcher cannot be null.");
+		this.delegate = delegate;
+		this.log = LoggerFactory.getLogger(delegate.getClass());
+	}
+
+	@Override
+	public boolean alive() {
+		return delegate.alive();
+	}
+
+	@Override
+	public boolean awaitAndShutdown() {
+		if(log.isTraceEnabled()) {
+			log.trace("awaitAndShutdown()");
+		}
+		return delegate.awaitAndShutdown();
+	}
+
+	@Override
+	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
+		if(log.isTraceEnabled()) {
+			log.trace("awaitAndShutdown({}, {})", timeout, timeUnit);
+		}
+		return delegate.awaitAndShutdown(timeout, timeUnit);
+	}
+
+	@Override
+	public void shutdown() {
+		if(log.isTraceEnabled()) {
+			log.trace("shutdown()");
+		}
+		delegate.shutdown();
+	}
+
+	@Override
+	public void halt() {
+		if(log.isTraceEnabled()) {
+			log.trace("halt()");
+		}
+		delegate.halt();
+	}
+
+	@Override
+	public <E extends Event<?>> void dispatch(Object key,
+	                                          E event,
+	                                          Registry<Consumer<? extends Event<?>>> consumerRegistry,
+	                                          Consumer<Throwable> errorConsumer,
+	                                          EventRouter eventRouter,
+	                                          Consumer<E> completionConsumer) {
+		if(log.isTraceEnabled()) {
+			log.trace("dispatch({}, {}, {}, {}, {}, {})",
+			          key,
+			          event,
+			          consumerRegistry,
+			          errorConsumer,
+			          eventRouter,
+			          completionConsumer);
+		}
+		delegate.dispatch(key, event, consumerRegistry, errorConsumer, eventRouter, completionConsumer);
+	}
+
+	@Override
+	public <E extends Event<?>> void dispatch(E event,
+	                                          EventRouter eventRouter,
+	                                          Consumer<E> consumer,
+	                                          Consumer<Throwable> errorConsumer) {
+		if(log.isTraceEnabled()) {
+			log.trace("dispatch({}, {}, {}, {})", event, eventRouter, consumer, errorConsumer);
+		}
+		delegate.dispatch(event, eventRouter, consumer, errorConsumer);
+	}
+
+	@Override
+	public void execute(Runnable command) {
+		delegate.execute(command);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/WorkQueueDispatcher.java b/reactor-core/src/main/java/reactor/event/dispatch/WorkQueueDispatcher.java
new file mode 100644
index 0000000..f677829
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/WorkQueueDispatcher.java
@@ -0,0 +1,147 @@
+package reactor.event.dispatch;
+
+import com.lmax.disruptor.*;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.function.Consumer;
+import reactor.support.NamedDaemonThreadFactory;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of a {@link Dispatcher} that uses a multi-threaded, multi-producer {@link RingBuffer} to queue tasks
+ * to execute.
+ *
+ * @author Jon Brisbin
+ * @since 1.1
+ */
+public class WorkQueueDispatcher extends AbstractMultiThreadDispatcher {
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+
+	private final ExecutorService           executor;
+	private final Disruptor<WorkQueueTask>  disruptor;
+	private final RingBuffer<WorkQueueTask> ringBuffer;
+
+	@SuppressWarnings("unchecked")
+	public WorkQueueDispatcher(String name,
+	                           int poolSize,
+	                           int backlog,
+	                           final Consumer<Throwable> uncaughtExceptionHandler) {
+		this(name, poolSize, backlog, uncaughtExceptionHandler, ProducerType.MULTI, new BlockingWaitStrategy());
+	}
+
+	@SuppressWarnings("unchecked")
+	public WorkQueueDispatcher(String name,
+	                           int poolSize,
+	                           int backlog,
+	                           final Consumer<Throwable> uncaughtExceptionHandler,
+	                           ProducerType producerType,
+	                           WaitStrategy waitStrategy) {
+		super(poolSize, backlog);
+		this.executor = Executors.newFixedThreadPool(
+				poolSize,
+				new NamedDaemonThreadFactory(name, getContext())
+		);
+		this.disruptor = new Disruptor<WorkQueueTask>(
+				new EventFactory<WorkQueueTask>() {
+					@Override
+					public WorkQueueTask newInstance() {
+						return new WorkQueueTask();
+					}
+				},
+				backlog,
+				executor,
+				producerType,
+				waitStrategy
+		);
+
+		this.disruptor.handleExceptionsWith(new ExceptionHandler() {
+			@Override
+			public void handleEventException(Throwable ex, long sequence, Object event) {
+				handleOnStartException(ex);
+			}
+
+			@Override
+			public void handleOnStartException(Throwable ex) {
+				if(null != uncaughtExceptionHandler) {
+					uncaughtExceptionHandler.accept(ex);
+				} else {
+					log.error(ex.getMessage(), ex);
+				}
+			}
+
+			@Override
+			public void handleOnShutdownException(Throwable ex) {
+				handleOnStartException(ex);
+			}
+		});
+
+		WorkHandler<WorkQueueTask>[] workHandlers = new WorkHandler[poolSize];
+		for(int i = 0; i < poolSize; i++) {
+			workHandlers[i] = new WorkHandler<WorkQueueTask>() {
+				@Override
+				public void onEvent(WorkQueueTask task) throws Exception {
+					task.run();
+				}
+			};
+		}
+		this.disruptor.handleEventsWithWorkerPool(workHandlers);
+
+		this.ringBuffer = disruptor.start();
+	}
+
+	@Override
+	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
+		shutdown();
+		try {
+			executor.awaitTermination(timeout, timeUnit);
+			disruptor.shutdown();
+		} catch(InterruptedException e) {
+			return false;
+		}
+		return true;
+	}
+
+	@Override
+	public void shutdown() {
+		executor.shutdown();
+		disruptor.shutdown();
+		super.shutdown();
+	}
+
+	@Override
+	public void halt() {
+		executor.shutdownNow();
+		disruptor.halt();
+		super.halt();
+	}
+
+	@Override
+	protected Task allocateTask() {
+		long seqId = ringBuffer.next();
+		return ringBuffer.get(seqId).setSequenceId(seqId);
+	}
+
+	protected void execute(Task task) {
+		ringBuffer.publish(((WorkQueueTask)task).getSequenceId());
+	}
+
+	private class WorkQueueTask extends MultiThreadTask {
+		private long sequenceId;
+
+		public long getSequenceId() {
+			return sequenceId;
+		}
+
+		public WorkQueueTask setSequenceId(long sequenceId) {
+			this.sequenceId = sequenceId;
+			return this;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/dispatch/package-info.java b/reactor-core/src/main/java/reactor/event/dispatch/package-info.java
new file mode 100644
index 0000000..d8bec43
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/dispatch/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * {@link reactor.event.dispatch.Dispatcher Dispatchers} provide a way for work to be done in another thread,
+ * but unlike a standard thread pool, do so with extreme efficiency.
+ */
+package reactor.event.dispatch;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/event/lifecycle/Pausable.java b/reactor-core/src/main/java/reactor/event/lifecycle/Pausable.java
new file mode 100644
index 0000000..7dadc80
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/lifecycle/Pausable.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.event.lifecycle;
+
+/**
+ * @author Stephane Maldini
+ */
+public interface Pausable {
+
+	/**
+	 * Cancel this {@literal Pausable}. The implementing component should never react to any stimulus,
+	 * closing resources if necessary.
+	 *
+	 * @return {@literal this}
+	 */
+	Pausable cancel();
+
+	/**
+	 * Pause this {@literal Pausable}. The implementing component should stop reacting, pausing resources if necessary.
+	 *
+	 * @return {@literal this}
+	 */
+	Pausable pause();
+
+	/**
+	 * Unpause this {@literal Pausable}. The implementing component should resume back from a previous pause,
+	 * re-activating resources if necessary.
+	 *
+	 * @return {@literal this}
+	 */
+	Pausable resume();
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/package-info.java b/reactor-core/src/main/java/reactor/event/package-info.java
new file mode 100644
index 0000000..2b907de
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Events are the foundational abstraction within Reactor that holds the data of the event plus any additional
+ * metadata about the event like the key used to notify the component and any user-specified headers.
+ */
+package reactor.event;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/event/registry/CachableRegistration.java b/reactor-core/src/main/java/reactor/event/registry/CachableRegistration.java
new file mode 100644
index 0000000..ef50863
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/registry/CachableRegistration.java
@@ -0,0 +1,111 @@
+package reactor.event.registry;
+
+import reactor.event.lifecycle.Pausable;
+import reactor.event.selector.ObjectSelector;
+import reactor.event.selector.Selector;
+
+/**
+ * @author Jon Brisbin
+ */
+public class CachableRegistration<T> implements Registration<T> {
+
+	private static final Selector NO_MATCH = new ObjectSelector<Void>(null) {
+		@Override
+		public boolean matches(Object key) {
+			return false;
+		}
+	};
+
+	private final Selector selector;
+	private final T        object;
+	private final Runnable onCancel;
+	private final boolean  lifecycle;
+
+	private volatile boolean cancelled      = false;
+	private volatile boolean cancelAfterUse = false;
+	private volatile boolean paused         = false;
+
+	public CachableRegistration(Selector selector, T object, Runnable onCancel) {
+		this.selector = selector;
+		this.object = object;
+		this.onCancel = onCancel;
+		this.lifecycle = Pausable.class.isAssignableFrom(object.getClass());
+	}
+
+	@Override
+	public Selector getSelector() {
+		return (!cancelled ? selector : NO_MATCH);
+	}
+
+	@Override
+	public T getObject() {
+		return (!cancelled && !paused ? object : null);
+	}
+
+	@Override
+	public Registration<T> cancelAfterUse() {
+		this.cancelAfterUse = true;
+		return this;
+	}
+
+	@Override
+	public boolean isCancelAfterUse() {
+		return cancelAfterUse;
+	}
+
+	@Override
+	public Registration<T> cancel() {
+		if (!cancelled) {
+			if (null != onCancel) {
+				onCancel.run();
+			}
+			if (lifecycle) {
+				((Pausable) object).cancel();
+			}
+			this.cancelled = true;
+		}
+		return this;
+	}
+
+	@Override
+	public boolean isCancelled() {
+		return cancelled;
+	}
+
+	@Override
+	public Registration<T> pause() {
+		this.paused = true;
+		if (lifecycle) {
+			((Pausable) object).pause();
+		}
+		return this;
+	}
+
+	@Override
+	public boolean isPaused() {
+		return paused;
+	}
+
+	@Override
+	public Registration<T> resume() {
+		paused = false;
+		if (lifecycle) {
+			((Pausable) object).resume();
+		}
+		return this;
+	}
+
+	@Override
+	public String toString() {
+		return "CachableRegistration{" +
+				"\n\tselector=" + selector +
+				",\n\tobject=" + object +
+				",\n\tonCancel=" + onCancel +
+				",\n\tlifecycle=" + lifecycle +
+				",\n\tcancelled=" + cancelled +
+				",\n\tcancelAfterUse=" + cancelAfterUse +
+				",\n\tpaused=" + paused +
+				"\n}";
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/registry/CachingRegistry.java b/reactor-core/src/main/java/reactor/event/registry/CachingRegistry.java
new file mode 100644
index 0000000..6f7437d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/registry/CachingRegistry.java
@@ -0,0 +1,164 @@
+package reactor.event.registry;
+
+import com.gs.collections.api.block.function.Function0;
+import com.gs.collections.api.block.procedure.Procedure;
+import com.gs.collections.api.list.MutableList;
+import com.gs.collections.impl.list.mutable.FastList;
+import com.gs.collections.impl.list.mutable.MultiReaderFastList;
+import com.gs.collections.impl.map.mutable.UnifiedMap;
+import jsr166e.ConcurrentHashMapV8;
+import reactor.event.selector.Selector;
+import reactor.function.Consumer;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Implementation of {@link reactor.event.registry.Registry} that uses a partitioned cache that partitions on thread
+ * id.
+ *
+ * @author Jon Brisbin
+ */
+public class CachingRegistry<T> implements Registry<T> {
+
+	private final NewThreadLocalRegsFn newThreadLocalRegsFn = new NewThreadLocalRegsFn();
+
+	private final boolean                                                                        useCache;
+	private final boolean                                                                        cacheNotFound;
+	private final Consumer<Object>                                                               onNotFound;
+	private final MultiReaderFastList<Registration<? extends T>>                                 registrations;
+	private final ConcurrentHashMapV8<Long, UnifiedMap<Object, List<Registration<? extends T>>>> threadLocalCache;
+
+	public CachingRegistry() {
+		this(true, true, null);
+	}
+
+	public CachingRegistry(boolean useCache, boolean cacheNotFound, Consumer<Object> onNotFound) {
+		this.useCache = useCache;
+		this.cacheNotFound=cacheNotFound;
+		this.onNotFound = onNotFound;
+		this.registrations = MultiReaderFastList.newList();
+		this.threadLocalCache = new ConcurrentHashMapV8<Long, UnifiedMap<Object, List<Registration<? extends T>>>>();
+	}
+
+	@Override
+	public <V extends T> Registration<V> register(Selector sel, V obj) {
+		RemoveRegistration removeFn = new RemoveRegistration();
+		final Registration<V> reg = new CachableRegistration<V>(sel, obj, removeFn);
+		removeFn.reg = reg;
+
+		registrations.withWriteLockAndDelegate(new Procedure<MutableList<Registration<? extends T>>>() {
+			@Override
+			public void value(MutableList<Registration<? extends T>> regs) {
+				regs.add(reg);
+			}
+		});
+		if (useCache) {
+			threadLocalCache.clear();
+		}
+
+		return reg;
+	}
+
+	@Override
+	public boolean unregister(final Object key) {
+		final AtomicBoolean modified = new AtomicBoolean(false);
+		registrations.withWriteLockAndDelegate(new Procedure<MutableList<Registration<? extends T>>>() {
+			@Override
+			public void value(final MutableList<Registration<? extends T>> regs) {
+				for (Registration<? extends T> reg : regs) {
+					if (reg.getSelector().matches(key)) {
+						regs.remove(reg);
+						modified.compareAndSet(false, true);
+					}
+				}
+				if (useCache && modified.get()) {
+					threadLocalCache.clear();
+				}
+			}
+		});
+		return modified.get();
+	}
+
+	@Override
+	public List<Registration<? extends T>> select(Object key) {
+		// use a thread-local cache
+		UnifiedMap<Object, List<Registration<? extends T>>> allRegs = threadLocalRegs();
+
+		// maybe pull Registrations from cache for this key
+		List<Registration<? extends T>> selectedRegs = null;
+		if (useCache && (null != (selectedRegs = allRegs.get(key)))) {
+			return selectedRegs;
+		}
+
+		// cache not used or cache miss
+		cacheMiss(key);
+		selectedRegs = FastList.newList();
+
+		// find Registrations based on Selector
+		for (Registration<? extends T> reg : this) {
+			if (reg.getSelector().matches(key)) {
+				selectedRegs.add(reg);
+			}
+		}
+		if (useCache && (!selectedRegs.isEmpty() || cacheNotFound)) {
+			allRegs.put(key, selectedRegs);
+		}
+
+		// nothing found, maybe invoke handler
+		if (selectedRegs.isEmpty() && (null != onNotFound)) {
+			onNotFound.accept(key);
+		}
+
+		// return
+		return selectedRegs;
+	}
+
+	@Override
+	public void clear() {
+		registrations.clear();
+		threadLocalCache.clear();
+	}
+
+	@Override
+	public Iterator<Registration<? extends T>> iterator() {
+		return FastList.<Registration<? extends T>>newList(registrations).iterator();
+	}
+
+	protected void cacheMiss(Object key) {
+	}
+
+	private UnifiedMap<Object, List<Registration<? extends T>>> threadLocalRegs() {
+		Long threadId = Thread.currentThread().getId();
+		UnifiedMap<Object, List<Registration<? extends T>>> regs;
+		if (null == (regs = threadLocalCache.get(threadId))) {
+			regs = threadLocalCache.computeIfAbsent(threadId, newThreadLocalRegsFn);
+		}
+		return regs;
+	}
+
+	private final class RemoveRegistration implements Runnable {
+		Registration<? extends T> reg;
+
+		@Override
+		public void run() {
+			registrations.withWriteLockAndDelegate(new Procedure<MutableList<Registration<? extends T>>>() {
+				@Override
+				public void value(MutableList<Registration<? extends T>> regs) {
+					regs.remove(reg);
+					threadLocalCache.clear();
+				}
+			});
+		}
+	}
+
+	private final class NewThreadLocalRegsFn
+			implements ConcurrentHashMapV8.Fun<Long, UnifiedMap<Object, List<Registration<? extends T>>>> {
+		@Override
+		public UnifiedMap<Object, List<Registration<? extends T>>> apply(Long aLong) {
+			return UnifiedMap.newMap();
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/registry/Registration.java b/reactor-core/src/main/java/reactor/event/registry/Registration.java
new file mode 100644
index 0000000..ab0e062
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/registry/Registration.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.registry;
+
+import reactor.event.lifecycle.Pausable;
+import reactor.event.selector.Selector;
+
+/**
+ * A {@code Registration} represents an object that has been {@link Registry#register(Selector,
+ * Object) registered} with a {@link Registry}.
+ *
+ * @param <T> The type of object that is registered
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ *
+ */
+public interface Registration<T> extends Pausable {
+
+	/**
+	 * The {@link reactor.event.selector.Selector} that was used when the registration was made.
+	 *
+	 * @return the registration's selector
+	 */
+	Selector getSelector();
+
+	/**
+	 * The object that was registered
+	 *
+	 * @return the registered object
+	 */
+	T getObject();
+
+	/**
+	 * Cancel this {@link Registration} after it has been selected and used. {@link
+	 * reactor.event.dispatch.Dispatcher} implementations should respect this value and perform
+	 * the cancellation.
+	 *
+	 * @return {@literal this}
+	 */
+	Registration<T> cancelAfterUse();
+
+	/**
+	 * Whether to cancel this {@link Registration} after use or not.
+	 *
+	 * @return {@literal true} if the registration will be cancelled after use, {@literal false}
+	 * otherwise.
+	 */
+	boolean isCancelAfterUse();
+
+	/**
+	 * Cancel this {@literal Registration} by removing it from its registry.
+	 *
+	 * @return {@literal this}
+	 */
+	@Override
+	Registration<T> cancel();
+
+	/**
+	 * Has this been cancelled?
+	 *
+	 * @return {@literal true} if this has been cancelled, {@literal false} otherwise.
+	 */
+	boolean isCancelled();
+
+	/**
+	 * Pause this {@literal Registration}. This leaves it in its registry but, while it is paused, it
+	 * will not be eligible for {@link Registry#select(Object) selection}.
+	 *
+	 * @return {@literal this}
+	 */
+	@Override
+	Registration<T> pause();
+
+	/**
+	 * Whether this {@literal Registration} has been paused or not.
+	 *
+	 * @return {@literal true} if currently paused, {@literal false} otherwise.
+	 */
+	boolean isPaused();
+
+	/**
+	 * Unpause this {@literal Registration}, making it available for {@link Registry#select(Object) selection}.
+	 *
+	 * @return {@literal this}
+	 */
+	@Override
+	Registration<T> resume();
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/registry/Registry.java b/reactor-core/src/main/java/reactor/event/registry/Registry.java
new file mode 100644
index 0000000..94769bf
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/registry/Registry.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.registry;
+
+import java.util.List;
+
+import reactor.event.selector.Selector;
+
+/**
+ * Implementations of this interface manage a registry of objects that works sort of like a Map, except Registries don't
+ * use simple keys, they use {@link reactor.event.selector.Selector}s to map their objects.
+ *
+ * @param <T> the type of objects that can be registered
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ * @author Stephane Maldini
+ */
+public interface Registry<T> extends Iterable<Registration<? extends T>> {
+
+	/**
+	 * Assign the given {@link reactor.event.selector.Selector} with the given object.
+	 *
+	 * @param sel The left-hand side of the {@literal Selector} comparison check.
+	 * @param obj The object to assign.
+	 * @return {@literal this}
+	 */
+	<V extends T> Registration<V> register(Selector sel, V obj);
+
+	/**
+	 * Remove any objects matching this {@code key}. This will unregister <b>all</b> objects matching the given
+	 * {@literal key}. There's no provision for removing only a specific object.
+	 *
+	 * @param key The key to be matched by the Selectors
+	 * @return {@literal true} if any objects were unassigned, {@literal false} otherwise.
+	 */
+	boolean unregister(Object key);
+
+	/**
+	 * Select {@link Registration}s whose {@link Selector} {@link Selector#matches(Object)} the given {@code key}.
+	 *
+	 * @param key The key for the Selectors to match
+	 * @return A {@link List} of {@link Registration}s whose {@link Selector} matches the given key.
+	 */
+	List<Registration<? extends T>> select(Object key);
+
+	/**
+	 * Clear the {@link Registry}, resetting its state and calling {@link Registration#cancel()} for any active {@link
+	 * Registration}.
+	 */
+	void clear();
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/registry/package-info.java b/reactor-core/src/main/java/reactor/event/registry/package-info.java
new file mode 100644
index 0000000..04da5b8
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/registry/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * {@link reactor.event.registry.Registry Registries} provide a common way to retrieve items registered using a
+ * {@link reactor.event.selector.Selector}.
+ */
+package reactor.event.registry;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/event/routing/ArgumentConvertingConsumerInvoker.java b/reactor-core/src/main/java/reactor/event/routing/ArgumentConvertingConsumerInvoker.java
new file mode 100644
index 0000000..e6f444f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/routing/ArgumentConvertingConsumerInvoker.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.routing;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import reactor.convert.Converter;
+import reactor.event.Event;
+import reactor.function.Consumer;
+
+/**
+ * This implementation of a {@link reactor.event.routing.ConsumerInvoker} will attempt to invoke
+ * a {@link Consumer} as-is and, if that fails with a {@link ClassCastException} because the
+ * argument declared in the {@literal Consumer} isn't of the correct type, it tries to find an
+ * object of that type in the array of possible arguments passed to the invoker. If that fails,
+ * it will attempt to use a {@link Converter} to convert the argument into a form acceptable to
+ * the {@literal Consumer}. If the argument is of type {@link Event} and the data inside that
+ * event is of a compatible type with the argument to the consumer, this invoker will unwrap that
+ * {@literal Event} and try to invoke the consumer using the data itself.
+ * <p/>
+ * Finally, if the {@literal Consumer} also implements {@link Callable}, then it will invoke the {@link
+ * Callable#call()} method to obtain a return value and return that. Otherwise it will return
+ * {@literal null} or throw any raised exceptions.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public final class ArgumentConvertingConsumerInvoker implements ConsumerInvoker {
+
+	private static final ReentrantReadWriteLock           CACHE_LOCK       = new ReentrantReadWriteLock();
+	private static final ReentrantReadWriteLock.ReadLock  CACHE_READ_LOCK  = CACHE_LOCK.readLock();
+	private static final ReentrantReadWriteLock.WriteLock CACHE_WRITE_LOCK = CACHE_LOCK.writeLock();
+	private static final Map<String, Class<?>>            ARG_TYPE_CACHE   = new WeakHashMap<String, Class<?>>();
+
+	private final Converter converter;
+
+	/**
+	 * Creates a new {@code ArgumentConvertingConsumerInvoker} that will use the given
+	 * {@code converter} for any necessary argument conversion.
+	 *
+	 * @param converter The converter to be used
+	 */
+	public ArgumentConvertingConsumerInvoker(Converter converter) {
+		this.converter = converter;
+	}
+
+	@SuppressWarnings({"unchecked", "rawtypes"})
+	@Override
+	public <T> T invoke(Consumer<?> consumer,
+	                    Class<? extends T> returnType,
+	                    Object possibleArg) throws Exception {
+		try {
+			((Consumer) consumer).accept(possibleArg);
+		} catch (ClassCastException e) {
+			Class<?> argType = resolveArgType(consumer);
+			if (argType == Object.class) {
+				throw e;
+			}
+
+			// Try and find an argument when the list of possible arguments past the 1st
+			if (null != possibleArg) {
+				if (argType.isInstance(possibleArg)) {
+					// arg type matches a possible arg
+					return invoke(consumer, returnType, possibleArg);
+				} else if (null != converter && converter.canConvert(possibleArg.getClass(), argType)) {
+					// arg is convertible
+					return invoke(consumer, returnType, converter.convert(possibleArg, argType));
+				} else if (Event.class.isInstance(possibleArg)
+						&& null != ((Event<?>) possibleArg).getData()
+						&& argType.isInstance(((Event<?>) possibleArg).getData())) {
+					// Try unwrapping the Event data
+					return invoke(consumer, returnType, ((Event<?>) possibleArg).getData());
+				}
+
+				// Try unwrapping the Event data
+				if (Event.class.isInstance(possibleArg)) {
+					return invoke(consumer, returnType, ((Event) possibleArg).getData());
+				}
+			}
+
+
+			throw e;
+		}
+
+		if (Void.TYPE == returnType) {
+			return null;
+		}
+
+		if (consumer instanceof Callable) {
+			Object o = ((Callable<Object>) consumer).call();
+
+			if (null == o) {
+				return null;
+			}
+
+			if (returnType.isAssignableFrom(o.getClass())) {
+				return (T) o;
+			} else if (null != converter && converter.canConvert(o.getClass(), returnType)) {
+				return converter.convert(o, returnType);
+			} else {
+				throw new IllegalArgumentException("Cannot convert object of type " + o.getClass()
+						.getName() + " to " + returnType.getName());
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public boolean supports(Consumer<?> consumer) {
+		return true;
+	}
+
+
+	/**
+	 * Resolves the type of argument that can be {@link Consumer#accept accepted} by the
+	 * given {@code consumer}.
+	 *
+	 * @param consumer The consumer to examine
+	 * @return The type that can be accepted by the consumer
+	 */
+	@SuppressWarnings({"unchecked"})
+	public static <T> Class<? extends T> resolveArgType(Consumer<?> consumer) {
+		Class<? extends T> clazz;
+		CACHE_READ_LOCK.lock();
+		try {
+			clazz = (Class<? extends T>) ARG_TYPE_CACHE.get(consumer.getClass().getName());
+			if (null != clazz) {
+				return clazz;
+			}
+		} finally {
+			CACHE_READ_LOCK.unlock();
+		}
+
+		if (Event.class.isInstance(consumer) && null != ((Event<?>) consumer).getData()) {
+			return (Class<? extends T>) ((Event<?>) consumer).getData().getClass();
+		}
+
+		for (Type t : consumer.getClass().getGenericInterfaces()) {
+			if (t instanceof ParameterizedType) {
+				ParameterizedType pt = (ParameterizedType) t;
+				Type t1 = pt.getActualTypeArguments()[0];
+				if (t1 instanceof ParameterizedType) {
+					clazz = (Class<? extends T>) ((ParameterizedType) t1).getRawType();
+				} else if (t1 instanceof Class) {
+					clazz = (Class<? extends T>) t1;
+				}
+			}
+			if (null != clazz) {
+				CACHE_WRITE_LOCK.lock();
+				try {
+					ARG_TYPE_CACHE.put(consumer.getClass().getName(), clazz);
+				} finally {
+					CACHE_WRITE_LOCK.unlock();
+				}
+				break;
+			}
+		}
+
+		if (null == clazz) {
+			for (Method m : consumer.getClass().getDeclaredMethods()) {
+				if ("accept".equals(m.getName()) && m.getParameterTypes().length == 1) {
+					clazz = (Class<? extends T>) m.getParameterTypes()[0];
+					CACHE_WRITE_LOCK.lock();
+					try {
+						ARG_TYPE_CACHE.put(consumer.getClass().getName(), clazz);
+					} finally {
+						CACHE_WRITE_LOCK.unlock();
+					}
+					return clazz;
+				}
+			}
+		}
+
+		return clazz;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/routing/ConsumerFilteringEventRouter.java b/reactor-core/src/main/java/reactor/event/routing/ConsumerFilteringEventRouter.java
new file mode 100644
index 0000000..080cab2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/routing/ConsumerFilteringEventRouter.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.routing;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+import reactor.filter.Filter;
+import reactor.function.Consumer;
+import reactor.function.support.CancelConsumerException;
+import reactor.util.Assert;
+
+import java.util.List;
+
+/**
+ * An {@link reactor.event.routing.EventRouter} that {@link Filter#filter filters} consumers before routing events to
+ * them.
+ *
+ * @author Andy Wilkinson
+ * @author Stephane Maldini
+ */
+public class ConsumerFilteringEventRouter implements EventRouter {
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+	private final Filter          filter;
+	private final ConsumerInvoker consumerInvoker;
+
+	/**
+	 * Creates a new {@code ConsumerFilteringEventRouter} that will use the {@code filter} to filter consumers.
+	 *
+	 * @param filter          The filter to use. Must not be {@code null}.
+	 * @param consumerInvoker Used to invoke consumers. Must not be {@code null}.
+	 * @throws IllegalArgumentException if {@code filter} or {@code consumerInvoker} is null.
+	 */
+	public ConsumerFilteringEventRouter(Filter filter, ConsumerInvoker consumerInvoker) {
+		Assert.notNull(filter, "filter must not be null");
+		Assert.notNull(consumerInvoker, "consumerInvoker must not be null");
+
+		this.filter = filter;
+		this.consumerInvoker = consumerInvoker;
+	}
+
+	@Override
+	public void route(Object key,
+                    Event<?> event,
+	                  List<Registration<? extends Consumer<? extends Event<?>>>> consumers,
+	                  Consumer<?> completionConsumer,
+	                  Consumer<Throwable> errorConsumer) {
+		if (null != consumers && !consumers.isEmpty()) {
+			List<Registration<? extends Consumer<? extends Event<?>>>> regs = filter.filter(consumers, key);
+			int size = regs.size();
+			// old-school for loop is much more efficient than using an iterator
+			for (int i = 0; i < size; i++) {
+				Registration<? extends Consumer<? extends Event<?>>> reg = regs.get(i);
+
+				if (null == reg || reg.isCancelled() || reg.isPaused()) {
+					continue;
+				}
+				try {
+					if (null != reg.getSelector().getHeaderResolver()) {
+						event.getHeaders().setAll(reg.getSelector().getHeaderResolver().resolve(key));
+					}
+					consumerInvoker.invoke(reg.getObject(), Void.TYPE, event);
+				} catch (CancelConsumerException cancel) {
+					reg.cancel();
+				} catch (Throwable t) {
+					if (null != event.getErrorConsumer()) {
+						event.consumeError(t);
+					} else if (null != errorConsumer) {
+						errorConsumer.accept(t);
+					} else {
+						logger.error("Event routing failed for {}: {}", reg.getObject(), t.getMessage(), t);
+						if (RuntimeException.class.isInstance(t)) {
+							throw (RuntimeException) t;
+						} else {
+							throw new IllegalStateException(t);
+						}
+					}
+				} finally {
+					if (reg.isCancelAfterUse()) {
+						reg.cancel();
+					}
+				}
+			}
+		}
+		if (null != completionConsumer) {
+			try {
+				consumerInvoker.invoke(completionConsumer, Void.TYPE, event);
+			} catch (Exception e) {
+				if (null != errorConsumer) {
+					errorConsumer.accept(e);
+				} else {
+					logger.error("Completion Consumer {} failed: {}", completionConsumer, e.getMessage(), e);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns the {@code Filter} being used
+	 *
+	 * @return The {@code Filter}.
+	 */
+	public Filter getFilter() {
+		return filter;
+	}
+
+	/**
+	 * Returns the {@code ConsumerInvoker} being used by the event router
+	 *
+	 * @return The {@code ConsumerInvoker}.
+	 */
+	public ConsumerInvoker getConsumerInvoker() {
+		return consumerInvoker;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/routing/ConsumerInvoker.java b/reactor-core/src/main/java/reactor/event/routing/ConsumerInvoker.java
new file mode 100644
index 0000000..0e0ffa9
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/routing/ConsumerInvoker.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.routing;
+
+import reactor.function.Consumer;
+import reactor.support.Supports;
+
+/**
+ * Implementations of this interface are responsible for invoking a {@link reactor.function.Consumer} that may take into account
+ * automatic argument conversion, return values, and other situations that might be specific to a particular use-case.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public interface ConsumerInvoker extends Supports<Consumer<?>> {
+
+	/**
+	 * Invoke a {@link reactor.function.Consumer}.
+	 *
+	 * @param consumer     The {@link reactor.function.Consumer} to invoke.
+	 * @param returnType   If the {@link reactor.function.Consumer} also implements a value-returning type, convert it to this type before
+	 *                     returning.
+	 * @param possibleArg Possible argument that may or may not be used.
+	 * @param <T>          The return type.
+	 * @return A result if available, or {@literal null} otherwise.
+	 * @throws Exception
+	 */
+	<T> T invoke(Consumer<?> consumer,
+							 Class<? extends T> returnType,
+							 Object possibleArg) throws Exception;
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/routing/EventRouter.java b/reactor-core/src/main/java/reactor/event/routing/EventRouter.java
new file mode 100644
index 0000000..141669a
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/routing/EventRouter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.routing;
+
+import java.util.List;
+
+import reactor.function.Consumer;
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+
+/**
+ * An {@code EventRouter} is used to route an {@code Event} to {@link Consumer Consumers}.
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public interface EventRouter {
+
+	/**
+	 * Routes the {@code event}, triggered by a notification with the given {@code key} to the
+	 * {@code consumers}. Depending on the router implementation, zero or more of the consumers
+	 * will receive the event. Upon successful completion of the event routing, the
+	 * {@code completionConsumer} will be invoked. {@code completionConsumer} may be null. In the
+	 * event of an exception during routing the {@code errorConsumer} is invoked.
+	 * {@code errorConsumer} may be null, in which case the exception is swallowed.
+	 *
+	 * @param key The notification key
+	 * @param event The {@code Event} to route
+	 * @param consumers The {@code Consumer}s to route the event to.
+	 * @param completionConsumer The {@code Consumer} to invoke upon successful completion of event routing
+	 * @param errorConsumer The {@code Consumer} to invoke when an error occurs during event routing
+	 */
+	void route(Object key, Event<?> event, List<Registration<? extends Consumer<? extends Event<?>>>> consumers, Consumer<?> completionConsumer, Consumer<Throwable> errorConsumer);
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/event/routing/TraceableDelegatingEventRouter.java b/reactor-core/src/main/java/reactor/event/routing/TraceableDelegatingEventRouter.java
new file mode 100644
index 0000000..7b9b6ea
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/routing/TraceableDelegatingEventRouter.java
@@ -0,0 +1,37 @@
+package reactor.event.routing;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+import reactor.function.Consumer;
+import reactor.util.Assert;
+
+import java.util.List;
+
+/**
+ * @author Jon Brisbin
+ */
+public class TraceableDelegatingEventRouter implements EventRouter {
+
+	private final EventRouter delegate;
+	private final Logger      log;
+
+	public TraceableDelegatingEventRouter(EventRouter delegate) {
+		Assert.notNull(delegate, "Delegate EventRouter cannot be null.");
+		this.delegate = delegate;
+		this.log = LoggerFactory.getLogger(delegate.getClass());
+	}
+
+	@Override
+	public void route(Object key,
+	                  Event<?> event,
+	                  List<Registration<? extends Consumer<? extends Event<?>>>> consumers,
+	                  Consumer<?> completionConsumer,
+	                  Consumer<Throwable> errorConsumer) {
+		if(log.isTraceEnabled()) {
+			log.trace("route({}, {}, {}, {}, {})", key, event, consumers, completionConsumer, errorConsumer);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/routing/package-info.java b/reactor-core/src/main/java/reactor/event/routing/package-info.java
new file mode 100644
index 0000000..35cb872
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/routing/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Event routing means sending a given {@link reactor.event.Event} to the correct {@link reactor.function.Consumer
+ * Consumers}.
+ */
+package reactor.event.routing;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/event/selector/ClassSelector.java b/reactor-core/src/main/java/reactor/event/selector/ClassSelector.java
new file mode 100644
index 0000000..dabaecd
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/ClassSelector.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.selector;
+
+
+/**
+ * Implementation of {@link Selector} that uses {@link Class#isAssignableFrom(Class)} to determine a match.
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ * @author Stephane Maldini
+ */
+public class ClassSelector extends ObjectSelector<Class<?>> {
+
+	/**
+	 * Creates a new ClassSelector that will match keys that are the same as, or are a
+	 * super type of the given {@code type}, i.e. the key is assignable according to
+	 * {@link Class#isAssignableFrom(Class)}.
+	 *
+	 * @param type The type to match
+	 */
+	public ClassSelector(Class<?> type) {
+		super(type);
+	}
+
+	/**
+	 * Creates a {@code ClassSelector} based on the given class type that only matches if the
+	 * key being matched is assignable according to {@link Class#isAssignableFrom(Class)}.
+	 *
+	 * @param supertype The supertype to compare.
+	 *
+	 * @return The new {@link Selector}.
+	 */
+	public static Selector typeSelector(Class<?> supertype) {
+		return new ClassSelector(supertype);
+	}
+
+	@Override
+	public boolean matches(Object key) {
+		return Class.class.isInstance(key) && getObject().isAssignableFrom((Class<?>) key);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/HeaderResolver.java b/reactor-core/src/main/java/reactor/event/selector/HeaderResolver.java
new file mode 100644
index 0000000..546527b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/HeaderResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.selector;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+
+/**
+ * Responsible for extracting any applicable headers from a key.
+ *
+ * @author Jon Brisbin
+ */
+public interface HeaderResolver {
+
+	/**
+	 * Resolve the headers that might be encoded in a key.
+	 *
+	 * @param key The key to match.
+	 *
+	 * @return Any applicable headers. Might be {@literal null}.
+	 */
+	@Nullable
+	Map<String, Object> resolve(Object key);
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/MatchAllSelector.java b/reactor-core/src/main/java/reactor/event/selector/MatchAllSelector.java
new file mode 100644
index 0000000..fc499ac
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/MatchAllSelector.java
@@ -0,0 +1,25 @@
+package reactor.event.selector;
+
+/**
+ * Implementation of {@link reactor.event.selector.Selector} that matches
+ * all objects.
+ *
+ * @author Michael Klishin
+ */
+public class MatchAllSelector implements Selector {
+
+    @Override
+    public Object getObject() {
+        return null;
+    }
+
+    @Override
+    public boolean matches(Object key) {
+        return true;
+    }
+
+    @Override
+    public HeaderResolver getHeaderResolver() {
+        return null;
+    }
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/ObjectSelector.java b/reactor-core/src/main/java/reactor/event/selector/ObjectSelector.java
new file mode 100644
index 0000000..198dc0b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/ObjectSelector.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.selector;
+
+/**
+ * {@link Selector} implementation that uses the {@link #hashCode()} and {@link #equals(Object)}
+ * methods of the internal object to determine a match.
+ *
+ * @param <T>
+ * 		The type of object held by the selector
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ * @author Stephane Maldini
+ */
+public class ObjectSelector<T> implements Selector {
+
+	private final Object monitor = new Object();
+	private final T object;
+
+	/**
+	 * Create a new {@link Selector} instance from the given object.
+	 *
+	 * @param object
+	 * 		The object to wrap.
+	 */
+	public ObjectSelector(T object) {
+		this.object = object;
+	}
+
+	/**
+	 * Helper method to create a {@link Selector} from the given object.
+	 *
+	 * @param obj
+	 * 		The object to wrap.
+	 * @param <T>
+	 * 		The type of the object.
+	 *
+	 * @return The new {@link Selector}.
+	 */
+	public static <T> Selector objectSelector(T obj) {
+		return new ObjectSelector<T>(obj);
+	}
+
+	@Override
+	public T getObject() {
+		return object;
+	}
+
+	@Override
+	public boolean matches(Object key) {
+		return !(null == object && null != key) && (object != null && object.equals(key));
+	}
+
+	@Override
+	public HeaderResolver getHeaderResolver() {
+		return null;
+	}
+
+	@Override
+	protected Object clone() throws CloneNotSupportedException {
+		return new ObjectSelector<T>(object);
+	}
+
+	@Override
+	public String toString() {
+		synchronized(monitor) {
+			return "Selector{" +
+					"object=" + object +
+					'}';
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/PredicateSelector.java b/reactor-core/src/main/java/reactor/event/selector/PredicateSelector.java
new file mode 100644
index 0000000..6f59400
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/PredicateSelector.java
@@ -0,0 +1,33 @@
+package reactor.event.selector;
+
+import reactor.function.Predicate;
+
+/**
+ * Implementation of {@link Selector} that delegates the work of matching an object to the given {@link Predicate}.
+ *
+ * @author Jon Brisbin
+ */
+public class PredicateSelector extends ObjectSelector<Predicate<Object>> {
+
+	public PredicateSelector(Predicate<Object> object) {
+		super(object);
+	}
+
+	/**
+	 * Creates a {@link Selector} based on the given {@link Predicate}.
+	 *
+	 * @param predicate
+	 * 		The {@link Predicate} to delegate to when matching objects.
+	 *
+	 * @return PredicateSelector
+	 */
+	public static PredicateSelector predicateSelector(Predicate<Object> predicate) {
+		return new PredicateSelector(predicate);
+	}
+
+	@Override
+	public boolean matches(Object key) {
+		return getObject().test(key);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/RegexSelector.java b/reactor-core/src/main/java/reactor/event/selector/RegexSelector.java
new file mode 100644
index 0000000..794d8b7
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/RegexSelector.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.selector;
+
+import javax.annotation.Nullable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A {@link Selector} implementation based on the given regular expression. Parses it into a {@link Pattern} for
+ * efficient matching against keys.
+ * <p/>
+ * An example of creating a regex Selector would be:
+ * <p/>
+ * <code>Selectors.R("event([0-9]+)")</code>
+ * <p/>
+ * This would match keys like:
+ * <p/>
+ * <code>"event1"</code>, <code>"event23"</code>, or <code>"event9"</code>
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ */
+public class RegexSelector extends ObjectSelector<Pattern> {
+
+	private final HeaderResolver headerResolver = new HeaderResolver() {
+		@Nullable
+		@Override
+		public Map<String, Object> resolve(Object key) {
+			Matcher m = getObject().matcher(key.toString());
+			if(!m.matches()) {
+				return null;
+			}
+			int groups = m.groupCount();
+			Map<String, Object> headers = new HashMap<String, Object>();
+			for(int i = 1; i <= groups; i++) {
+				String name = "group" + i;
+				String value = m.group(i);
+				headers.put(name, value);
+			}
+			return headers;
+		}
+	};
+
+	/**
+	 * Create a {@link Selector} when the given regex pattern.
+	 *
+	 * @param pattern
+	 * 		The regex String that will be compiled into a {@link Pattern}.
+	 */
+	public RegexSelector(String pattern) {
+		super(Pattern.compile(pattern));
+	}
+
+	/**
+	 * Creates a {@link Selector} based on the given regular expression.
+	 *
+	 * @param regex
+	 * 		The regular expression to compile.
+	 *
+	 * @return The new {@link Selector}.
+	 */
+	public static Selector regexSelector(String regex) {
+		return new RegexSelector(regex);
+	}
+
+	@Override
+	public boolean matches(Object key) {
+		return key instanceof String
+				&& getObject().matcher((String)key).matches();
+	}
+
+	@Override
+	public HeaderResolver getHeaderResolver() {
+		return headerResolver;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/Selector.java b/reactor-core/src/main/java/reactor/event/selector/Selector.java
new file mode 100644
index 0000000..333324b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/Selector.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.selector;
+
+/**
+ * A {@literal Selector} is a wrapper around an arbitrary object.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+public interface Selector  {
+
+
+	/**
+	 * Get the object being used for comparisons and equals checks.
+	 *
+	 * @return The internal object.
+	 */
+	Object getObject();
+
+	/**
+	 * Indicates whether this Selector matches the {@code key}.
+	 *
+	 * @param key The key to match
+	 *
+	 * @return {@code true} if there's a match, otherwise {@code false}.
+	 */
+	boolean matches(Object key);
+
+	/**
+	 * Return a component that can resolve headers from a key
+	 *
+	 * @return A {@link HeaderResolver} applicable to this {@link Selector} type.
+	 */
+	HeaderResolver getHeaderResolver();
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/Selectors.java b/reactor-core/src/main/java/reactor/event/selector/Selectors.java
new file mode 100644
index 0000000..28ff867
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/Selectors.java
@@ -0,0 +1,238 @@
+package reactor.event.selector;
+
+import reactor.function.Predicate;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper methods for creating {@link Selector}s.
+ *
+ * @author Andy Wilkinson
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ *
+ */
+public abstract class Selectors {
+
+	private static final AtomicInteger HASH_CODES = new AtomicInteger(Integer.MIN_VALUE);
+
+	/**
+	 * Creates an anonymous {@link reactor.event.selector.Selector}.
+	 *
+	 * @return a new Selector
+	 *
+	 * @see ObjectSelector
+	 */
+	public static Selector anonymous() {
+		Object obj = new AnonymousKey();
+		return $(obj);
+	}
+
+	/**
+	 * A short-hand alias for {@link Selectors#anonymous()}.
+	 * <p/>
+	 * Creates an anonymous {@link reactor.event.selector.Selector}.
+	 *
+	 * @return a new Selector
+	 *
+	 * @see ObjectSelector
+	 */
+	public static Selector $() {
+		return anonymous();
+	}
+
+	/**
+	 * Creates a {@link Selector} based on the given object.
+	 *
+	 * @param obj
+	 * 		The object to use for matching
+	 *
+	 * @return The new {@link ObjectSelector}.
+	 *
+	 * @see ObjectSelector
+	 */
+	public static <T> Selector object(T obj) {
+		return new ObjectSelector<T>(obj);
+	}
+
+	/**
+	 * A short-hand alias for {@link Selectors#object}.
+	 * <p/>
+	 * Creates a {@link Selector} based on the given object.
+	 *
+	 * @param obj
+	 * 		The object to use for matching
+	 *
+	 * @return The new {@link ObjectSelector}.
+	 *
+	 * @see ObjectSelector
+	 */
+	public static <T> Selector $(T obj) {
+		return object(obj);
+	}
+
+	/**
+	 * Creates a {@link Selector} based on the given string format and arguments.
+	 *
+	 * @param fmt
+	 * 		The {@code String.format} style format specification
+	 * @param args
+	 * 		The format args
+	 *
+	 * @return The new {@link ObjectSelector}.
+	 *
+	 * @see ObjectSelector
+	 * @see String#format(String, Object...)
+	 */
+	public static Selector $(String fmt, Object... args) {
+		return object(String.format(fmt, args));
+	}
+
+	/**
+	 * Creates a {@link Selector} based on the given regular expression.
+	 *
+	 * @param regex
+	 * 		The regular expression to compile and use for matching
+	 *
+	 * @return The new {@link RegexSelector}.
+	 *
+	 * @see RegexSelector
+	 */
+	public static Selector regex(String regex) {
+		return new RegexSelector(regex);
+	}
+
+	/**
+	 * A short-hand alias for {@link Selectors#regex(String)}.
+	 * <p/>
+	 * Creates a {@link Selector} based on the given regular expression.
+	 *
+	 * @param regex
+	 * 		The regular expression to compile and use for matching
+	 *
+	 * @return The new {@link RegexSelector}.
+	 *
+	 * @see RegexSelector
+	 */
+	public static Selector R(String regex) {
+		return new RegexSelector(regex);
+	}
+
+	/**
+	 * Creates a {@link Selector} based on the given class type that matches objects whose type is
+	 * assignable according to {@link Class#isAssignableFrom(Class)}.
+	 *
+	 * @param supertype
+	 * 		The supertype to use for matching
+	 *
+	 * @return The new {@link ClassSelector}.
+	 *
+	 * @see ClassSelector
+	 */
+	public static Selector type(Class<?> supertype) {
+		return new ClassSelector(supertype);
+	}
+
+	/**
+	 * A short-hand alias for {@link Selectors#type(Class)}.
+	 * <p/>
+	 * Creates a {@link Selector} based on the given class type that matches objects whose type is
+	 * assignable according to {@link Class#isAssignableFrom(Class)}.
+	 *
+	 * @param supertype
+	 * 		The supertype to compare.
+	 *
+	 * @return The new {@link ClassSelector}.
+	 *
+	 * @see ClassSelector
+	 */
+	public static Selector T(Class<?> supertype) {
+		return type(supertype);
+	}
+
+	/**
+	 * Creates a {@link Selector} based on a URI template.
+	 *
+	 * @param uri
+	 * 		The string to compile into a URI template and use for matching
+	 *
+	 * @return The new {@link UriPathSelector}.
+	 *
+	 * @see UriPathTemplate
+	 * @see UriPathSelector
+	 */
+	public static Selector uri(String uri) {
+		if(null == uri) {
+			return null;
+		}
+		switch(uri.charAt(0)) {
+			case '/':
+				return new UriPathSelector(uri);
+			default:
+				return new UriSelector(uri);
+		}
+	}
+
+	/**
+	 * A short-hand alias for {@link Selectors#uri(String)}.
+	 * <p/>
+	 * Creates a {@link Selector} based on a URI template.
+	 *
+	 * @param uri
+	 * 		The string to compile into a URI template and use for matching
+	 *
+	 * @return The new {@link UriPathSelector}.
+	 *
+	 * @see UriPathTemplate
+	 * @see UriPathSelector
+	 */
+	public static Selector U(String uri) {
+		return uri(uri);
+	}
+
+	/**
+	 * Creates a {@link Selector} based on the given {@link Predicate}.
+	 *
+	 * @param predicate
+	 * 		The {@link Predicate} to delegate to when matching objects.
+	 *
+	 * @return PredicateSelector
+	 *
+	 * @see PredicateSelector
+	 */
+	public static Selector predicate(Predicate<Object> predicate) {
+		return new PredicateSelector(predicate);
+	}
+
+    /**
+     * Creates a {@link reactor.event.selector.Selector} that matches
+     * all objects.
+     * @return The new {@link reactor.event.selector.MatchAllSelector}
+     *
+     * @see reactor.event.selector.MatchAllSelector
+     */
+    public static Selector matchAll() {
+        return new MatchAllSelector();
+    }
+
+	/**
+	 * Creates a {@link reactor.event.selector.Selector} that matches
+	 * objects on set membership.
+	 * @return The new {@link SetMembershipSelector}
+	 *
+	 * @see SetMembershipSelector
+	 */
+	public static Selector setMembership(Set set) {
+		return new SetMembershipSelector(set);
+	}
+
+	public static class AnonymousKey {
+		private final int hashCode = HASH_CODES.getAndIncrement() << 2;
+
+		@Override
+		public int hashCode() {
+			return hashCode;
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/SetMembershipSelector.java b/reactor-core/src/main/java/reactor/event/selector/SetMembershipSelector.java
new file mode 100644
index 0000000..8f687dc
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/SetMembershipSelector.java
@@ -0,0 +1,38 @@
+package reactor.event.selector;
+
+import java.util.Set;
+
+/**
+ * Implementation of {@link reactor.event.selector.Selector} that matches
+ * objects on set membership.
+ *
+ * @author Michael Klishin
+ */
+public class SetMembershipSelector implements Selector {
+	private final Set set;
+
+	/**
+	 * Create a {@link Selector} when the given regex pattern.
+	 *
+	 * @param set
+	 * 		The {@link Set} that will be used for membership checks.
+	 */
+	public SetMembershipSelector(Set set) {
+		this.set = set;
+	}
+
+	@Override
+	public Object getObject() {
+		return this.set;
+	}
+
+	@Override
+	public boolean matches(Object key) {
+		return this.set.contains(key);
+	}
+
+	@Override
+	public HeaderResolver getHeaderResolver() {
+		return null;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/UriPathSelector.java b/reactor-core/src/main/java/reactor/event/selector/UriPathSelector.java
new file mode 100644
index 0000000..5f5d4db
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/UriPathSelector.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.selector;
+
+import javax.annotation.Nullable;
+import java.util.Map;
+
+/**
+ * A {@link Selector} implementation based on a {@link UriPathTemplate}.
+ *
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ *
+ * @see UriPathTemplate
+ */
+public class UriPathSelector extends ObjectSelector<UriPathTemplate> {
+
+	private final HeaderResolver headerResolver = new HeaderResolver() {
+		@Nullable
+		@Override
+		public Map<String, Object> resolve(Object key) {
+			Map<String, Object> headers = getObject().match(key.toString());
+			if (null != headers && !headers.isEmpty()) {
+				return headers;
+			}
+			return null;
+		}
+	};
+
+	/**
+	 * Create a selector from the given uri template string.
+	 *
+	 * @param uriPathTmpl The string to compile into a {@link UriPathTemplate}.
+	 */
+	public UriPathSelector(String uriPathTmpl) {
+		super(new UriPathTemplate(uriPathTmpl));
+	}
+
+	/**
+	 * Creates a {@link Selector} based on a URI template.
+	 *
+	 * @param uriTemplate The URI template to compile.
+	 *
+	 * @return The new {@link Selector}.
+	 *
+	 * @see UriPathTemplate
+	 */
+	public static Selector uriPathSelector(String uriTemplate) {
+		return new UriPathSelector(uriTemplate);
+	}
+
+	@Override
+	public boolean matches(Object key) {
+		if (!(key instanceof String)) {
+			return false;
+		}
+
+		String path = (String) key;
+		return getObject().matches(path);
+	}
+
+	@Override
+	public HeaderResolver getHeaderResolver() {
+		return headerResolver;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/UriPathTemplate.java b/reactor-core/src/main/java/reactor/event/selector/UriPathTemplate.java
new file mode 100644
index 0000000..aeeff8c
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/UriPathTemplate.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.selector;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a URI template. A URI template is a URI-like String that contains variables enclosed by braces
+ * (<code>{</code>, <code>}</code>), which can be expanded to produce an actual URI.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @author Jon Brisbin
+ * @see <a href="http://bitworking.org/projects/URI-Templates/">URI Templates</a>
+ */
+public class UriPathTemplate {
+
+	private static final Pattern FULL_SPLAT_PATTERN     = Pattern.compile("[\\*][\\*]");
+	private static final String  FULL_SPLAT_REPLACEMENT = ".*";
+
+	private static final Pattern NAME_SPLAT_PATTERN     = Pattern.compile("\\{([^/]+?)\\}[\\*][\\*]");
+	// TODO: JDK 6 doesn't support named capture groups
+	//private static final String  NAME_SPLAT_REPLACEMENT = "(?<%NAME%>.*)";
+	private static final String  NAME_SPLAT_REPLACEMENT = "(.*)";
+
+	private static final Pattern NAME_PATTERN     = Pattern.compile("\\{([^/]+?)\\}");
+	// TODO: JDK 6 doesn't support named capture groups
+	//private static final String  NAME_REPLACEMENT = "(?<%NAME%>[^\\/.]*)";
+	private static final String  NAME_REPLACEMENT = "([^\\/.]*)";
+
+	private final List<String>                         pathVariables = new ArrayList<String>();
+	private final HashMap<String, Matcher>             matchers      = new HashMap<String, Matcher>();
+	private final HashMap<String, Map<String, Object>> vars          = new HashMap<String, Map<String, Object>>();
+
+	private final Pattern uriPattern;
+
+	/**
+	 * Creates a new {@code UriPathTemplate} from the given {@code uriPattern}.
+	 *
+	 * @param uriPattern The pattern to be used by the template
+	 */
+	public UriPathTemplate(String uriPattern) {
+		String s = "^" + uriPattern;
+
+		Matcher m = NAME_SPLAT_PATTERN.matcher(s);
+		while (m.find()) {
+			for (int i = 1; i <= m.groupCount(); i++) {
+				String name = m.group(i);
+				pathVariables.add(name);
+				s = m.replaceFirst(NAME_SPLAT_REPLACEMENT.replaceAll("%NAME%", name));
+				m.reset(s);
+			}
+		}
+
+		m = NAME_PATTERN.matcher(s);
+		while (m.find()) {
+			for (int i = 1; i <= m.groupCount(); i++) {
+				String name = m.group(i);
+				pathVariables.add(name);
+				s = m.replaceFirst(NAME_REPLACEMENT.replaceAll("%NAME%", name));
+				m.reset(s);
+			}
+		}
+
+		m = FULL_SPLAT_PATTERN.matcher(s);
+		while (m.find()) {
+			s = m.replaceAll(FULL_SPLAT_REPLACEMENT);
+			m.reset(s);
+		}
+
+		this.uriPattern = Pattern.compile(s + "$");
+	}
+
+	/**
+	 * Tests the given {@code uri} against this template, returning {@code true} if the
+	 * uri matches the template, {@code false} otherwise.
+	 *
+	 * @param uri The uri to match
+	 *
+	 * @return {@code true} if there's a match, {@code false} otherwise
+	 */
+	public boolean matches(String uri) {
+		return matcher(uri).matches();
+	}
+
+	/**
+	 * Matches the template against the given {@code uri} returning a map of path parameters
+	 * extracted from the uri, keyed by the names in the template. If the uri does not match,
+	 * or there are no path parameters, an empty map is returned.
+	 *
+	 * @param uri The uri to match
+	 *
+	 * @return the path parameters from the uri. Never {@code null}.
+	 */
+	public Map<String, Object> match(String uri) {
+		Map<String, Object> pathParameters = vars.get(uri);
+		if (null != pathParameters) {
+			return pathParameters;
+		}
+
+		pathParameters = new HashMap<String, Object>();
+		Matcher m = matcher(uri);
+		if (m.matches()) {
+			int i = 1;
+			for (String name : pathVariables) {
+				String val = m.group(i++);
+				pathParameters.put(name, val);
+			}
+		}
+		synchronized (vars) {
+			vars.put(uri, pathParameters);
+		}
+
+		return pathParameters;
+	}
+
+	private Matcher matcher(String uri) {
+		Matcher m = matchers.get(uri);
+		if (null == m) {
+			m = uriPattern.matcher(uri);
+			synchronized (matchers) {
+				matchers.put(uri, m);
+			}
+		}
+		return m;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/UriSelector.java b/reactor-core/src/main/java/reactor/event/selector/UriSelector.java
new file mode 100644
index 0000000..d8b0292
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/UriSelector.java
@@ -0,0 +1,163 @@
+package reactor.event.selector;
+
+import javax.annotation.Nullable;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A {@link reactor.event.selector.Selector} implementation that matches on various components of a full URI.
+ * <p>
+ * The {@code UriSelector} will explode a URI into its component parts. If you use the following as a selector:
+ * <pre><code>
+ * reactor.on(U("scheme://host/path"), ...);
+ * </code></pre>
+ * Then any {@link java.net.URI} or String that matches those elements will be matched.
+ * </p>
+ * <p>
+ * It's not an exact string match but a match on components because you can pass parameters in the form of a query
+ * string. Consider the following example:
+ * <pre><code>
+ * reactor.on(U("tcp://*:3000/topic"), consumer);
+ * </code></pre>
+ * This means "match any URIs using scheme 'tcp' to port '3000' for the path '/topic' irregardless of host". When a URI
+ * is sent as the key to a notify like this:
+ * <pre><code>
+ * reactor.notify("tcp://user:ENCODEDPWD@192.168.0.10:3000/topic?param=value"), event);
+ * </code></pre>
+ * The match will succeed and the headers will be set as follows:
+ * <ul>
+ * <li>{@code scheme}: "tcp"</li>
+ * <li>{@code authorization}: "user:ENCODEDPWD at 192.168.0.10:3000"</li>
+ * <li>{@code userInfo}: "user:ENCODEDPWD"</li>
+ * <li>{@code host}: "192.168.0.10"</li>
+ * <li>{@code port}: "3000"</li>
+ * <li>{@code path}: "/topic"</li>
+ * <li>{@code fragment}: {@code null}</li>
+ * <li>{@code query}: "param=value"</li>
+ * <li>{@code param}: "value"</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Wildcards can be used in place of {@code host}, and {@code path}. The former by replacing the host with '*' and the
+ * latter by replacing the path with '/*'.
+ * </p>
+ *
+ * @author Jon Brisbin
+ */
+public class UriSelector extends ObjectSelector<URI> {
+
+	private static final UriHeaderResolver URI_HEADER_RESOLVER = new UriHeaderResolver();
+
+	private final String scheme;
+	private final String host;
+	private final int    port;
+	private final String path;
+	private final String fragment;
+
+	public UriSelector(String uri) {
+		this(URI.create(uri));
+	}
+
+	public UriSelector(URI uri) {
+		super(uri);
+		scheme = (null != uri.getScheme() ? uri.getScheme() : "*");
+		String authority = uri.getAuthority();
+		host = (null != uri.getHost() ? uri.getHost() : "*");
+		if(authority.contains("*:")) {
+			int i = authority.lastIndexOf(":") + 1;
+			if(i > 1) {
+				port = Integer.parseInt(authority.substring(i));
+			} else {
+				port = -1;
+			}
+		} else {
+			port = uri.getPort();
+		}
+		path = (null != uri.getPath() ? uri.getPath() : "/*");
+		fragment = uri.getFragment();
+	}
+
+	@Override
+	public HeaderResolver getHeaderResolver() {
+		return URI_HEADER_RESOLVER;
+	}
+
+	@Override
+	public boolean matches(Object key) {
+		if(null == key) {
+			return false;
+		}
+
+		URI uri = objectToURI(key);
+
+		if(uri == null){
+			return false;
+		}
+
+		boolean schemeMatches = "*".equals(scheme) || scheme.equals(uri.getScheme());
+		boolean hostMatches = "*".equals(host) || host.equals(uri.getHost());
+		boolean portMatches = -1 == port || port == uri.getPort();
+		boolean pathMatches = "/*".equals(path) || path.equals(uri.getPath());
+		boolean fragmentMatches = null == fragment || fragment.equals(uri.getFragment());
+
+		return schemeMatches
+				&& hostMatches
+				&& portMatches
+				&& pathMatches
+				&& fragmentMatches;
+	}
+
+	private static URI objectToURI(Object key) {
+		if(key instanceof URI) {
+			return (URI)key;
+		} else if(key instanceof String) {
+			return URI.create(key.toString());
+		} else {
+			return null;
+		}
+	}
+
+	private static class UriHeaderResolver implements HeaderResolver {
+		@Nullable
+		@Override
+		public Map<String, Object> resolve(Object key) {
+			if(null == key) {
+				return null;
+			}
+
+			URI uri = objectToURI(key);
+
+			if(uri == null){
+				return null;
+			}
+
+			Map<String, Object> headers = new HashMap<String, Object>();
+
+			headers.put("authority", uri.getAuthority());
+			headers.put("fragment", uri.getFragment());
+			headers.put("host", uri.getHost());
+			headers.put("path", uri.getPath());
+			headers.put("port", String.valueOf(uri.getPort()));
+			headers.put("query", uri.getQuery());
+			if(null != uri.getQuery()) {
+				try {
+					String query = URLDecoder.decode(uri.getQuery(), "ISO-8859-1");
+					for(String s : query.split("&")) {
+						String[] parts = s.split("=");
+						headers.put(parts[0], parts[1]);
+					}
+				} catch(UnsupportedEncodingException e) {
+					throw new IllegalArgumentException(e);
+				}
+			}
+			headers.put("scheme", uri.getScheme());
+			headers.put("userInfo", uri.getUserInfo());
+
+			return headers;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/event/selector/package-info.java b/reactor-core/src/main/java/reactor/event/selector/package-info.java
new file mode 100644
index 0000000..4321e65
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/selector/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * {@link reactor.event.selector.Selector Selectors} provide a way to register components to react to given key data.
+ */
+package reactor.event.selector;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/event/support/CallbackEvent.java b/reactor-core/src/main/java/reactor/event/support/CallbackEvent.java
new file mode 100644
index 0000000..285305d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/support/CallbackEvent.java
@@ -0,0 +1,49 @@
+package reactor.event.support;
+
+import reactor.event.Event;
+import reactor.function.Consumer;
+
+/**
+ * Simple {@link Event} implementation that attaches a callback to an {@link Event} and
+ * passes it to a delegate {@link Consumer}.
+ *
+ * @param <T> the type of the event payload
+ *
+ * @author Stephane Maldini
+ */
+public class CallbackEvent<T> extends Event<T>{
+	private static final long serialVersionUID = -7173643160887108377L;
+	final Consumer callback;
+
+	public CallbackEvent(T data, Consumer callback) {
+		this(null, data, callback);
+	}
+
+	public CallbackEvent(Headers headers, T data, Consumer callback) {
+		this(headers, data, callback, null);
+	}
+
+	public CallbackEvent(Headers headers, T data, Consumer callback, Consumer<Throwable> throwableConsumer) {
+		super(headers, data, throwableConsumer);
+		this.callback = callback;
+	}
+
+	@Override
+	public <X> Event<X> copy(X data) {
+		if (null != getReplyTo())
+			return new CallbackEvent<X>(getHeaders(), data, callback, getErrorConsumer()).setReplyTo(getReplyTo());
+		else
+			return new CallbackEvent<X>(getHeaders(), data, callback, getErrorConsumer());
+	}
+
+
+	/**
+	 * Trigger callback with current payload
+	 */
+	@SuppressWarnings("unchecked")
+	public void callback(){
+		if(null != callback){
+			callback.accept(getData());
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/event/support/EventConsumer.java b/reactor-core/src/main/java/reactor/event/support/EventConsumer.java
new file mode 100644
index 0000000..ea3e67b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/event/support/EventConsumer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event.support;
+
+import reactor.function.Consumer;
+import reactor.event.Event;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Simple {@link Consumer} implementation that pulls the data from an {@link Event} and
+ * passes it to a delegate {@link Consumer}.
+ *
+ * @param <T> the type of the event that can be handled by the consumer and the type that
+ *            can be handled by the delegate
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class EventConsumer<T> implements Consumer<Event<T>> {
+
+	private final Consumer<T> delegate;
+
+	/**
+	 * Creates a new {@code EventConsumer} that will pass event data to the given {@code
+	 * delegate}.
+	 *
+	 * @param delegate The delegate consumer
+	 */
+	public EventConsumer(@Nonnull Consumer<T> delegate) {
+		Assert.notNull(delegate, "Delegate must not be null");
+		this.delegate = delegate;
+	}
+
+	@Override
+	public void accept(Event<T> ev) {
+		delegate.accept(ev.getData());
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/filter/AbstractFilter.java b/reactor-core/src/main/java/reactor/filter/AbstractFilter.java
new file mode 100644
index 0000000..3c151e2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/filter/AbstractFilter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.filter;
+
+import java.util.List;
+
+import reactor.util.Assert;
+
+abstract class AbstractFilter implements Filter {
+
+	@Override
+	public final <T> List<T> filter(List<T> items, Object key) {
+		Assert.notNull(items, "items must not be null");
+		return doFilter(items, key);
+	}
+
+	protected abstract <T> List<T> doFilter(List<T> items, Object key);
+
+}
diff --git a/reactor-core/src/main/java/reactor/filter/Filter.java b/reactor-core/src/main/java/reactor/filter/Filter.java
new file mode 100644
index 0000000..61de618
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/filter/Filter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.filter;
+
+import java.util.List;
+
+/**
+ * A {@code Filter} is used to filter a list of items. The nature of the filtering is determined by the
+ * implementation.
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public interface Filter {
+
+	/**
+	 * Filters the given {@code List} of {@code items}. The {@code key} may be used by an implementation to
+	 * influence the filtering.
+	 *
+	 * @param items The items to filter. Must not be {@code null}.
+	 * @param key The key
+	 *
+	 * @return The filtered items, never {@code null}.
+	 *
+	 * @throws IllegalArgumentException if {@code items} is null
+	 */
+	<T> List<T> filter(List<T> items, Object key);
+
+}
diff --git a/reactor-core/src/main/java/reactor/filter/FirstFilter.java b/reactor-core/src/main/java/reactor/filter/FirstFilter.java
new file mode 100644
index 0000000..f21b721
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/filter/FirstFilter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.filter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A {@link reactor.filter.Filter} implementation that returns the first item.
+ *
+ * @author Stephane Maldini
+ *
+ */
+public final class FirstFilter extends AbstractFilter {
+
+	@Override
+	public <T> List<T> doFilter(List<T> items, Object key) {
+		if (items.isEmpty()) {
+			return items;
+		} else {
+			return Collections.singletonList(items.get(0));
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/filter/PassThroughFilter.java b/reactor-core/src/main/java/reactor/filter/PassThroughFilter.java
new file mode 100644
index 0000000..6be9d30
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/filter/PassThroughFilter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.filter;
+
+import java.util.List;
+
+/**
+ * A {@link Filter} implementation that performs no filtering, returning the {@code items} as-is.
+ *
+ * @author Andy Wilkinson
+ * @author Stephane Maldini
+ */
+public final class PassThroughFilter extends AbstractFilter {
+
+	@Override
+	public <T> List<T> doFilter(List<T> items, Object key) {
+		return items;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/filter/RandomFilter.java b/reactor-core/src/main/java/reactor/filter/RandomFilter.java
new file mode 100644
index 0000000..547cf3c
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/filter/RandomFilter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.filter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * A {@link Filter} implementation that returns a single, randomly selected item.
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public final class RandomFilter extends AbstractFilter {
+
+	private final Random random = new Random();
+
+	@Override
+	public <T> List<T> doFilter(List<T> items, Object key) {
+		if (items.isEmpty()) {
+			return items;
+		} else {
+			return Collections.singletonList(items.get(random.nextInt(items.size())));
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/filter/RoundRobinFilter.java b/reactor-core/src/main/java/reactor/filter/RoundRobinFilter.java
new file mode 100644
index 0000000..608b0f1
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/filter/RoundRobinFilter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.filter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import reactor.util.Assert;
+
+/**
+ * A {@link Filter} implementation that returns a single item. The item is selected
+ * using a round-robin algorithm based on the number of times the {@code key} has been
+ * passed into the filter.
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public final class RoundRobinFilter extends AbstractFilter {
+
+	private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+
+	private final Lock readLock = readWriteLock.readLock();
+
+	private final Lock writeLock = readWriteLock.writeLock();
+
+	private final Map<Object, AtomicLong> usageCounts = new HashMap<Object, AtomicLong>();
+
+	@Override
+	public <T> List<T> doFilter(List<T> items, Object key) {
+		Assert.notNull(key, "'key' must not be null");
+		if (items.isEmpty()) {
+			return items;
+		} else {
+			int index = (int)(getUsageCount(key).getAndIncrement() % (items.size()));
+			return Collections.singletonList(items.get(index));
+		}
+	}
+
+	private AtomicLong getUsageCount(Object key) {
+		readLock.lock();
+		try {
+			AtomicLong usageCount = this.usageCounts.get(key);
+			if (usageCount == null) {
+				readLock.unlock();
+				writeLock.lock();
+				try {
+					usageCount = this.usageCounts.get(key);
+					if (usageCount == null) {
+						usageCount = new AtomicLong();
+						this.usageCounts.put(key, usageCount);
+					}
+				} finally {
+					writeLock.unlock();
+					readLock.lock();
+				}
+			}
+			return usageCount;
+		} finally {
+			readLock.unlock();
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/filter/TraceableDelegatingFilter.java b/reactor-core/src/main/java/reactor/filter/TraceableDelegatingFilter.java
new file mode 100644
index 0000000..f5dd950
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/filter/TraceableDelegatingFilter.java
@@ -0,0 +1,36 @@
+package reactor.filter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.util.Assert;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Jon Brisbin
+ */
+public class TraceableDelegatingFilter implements Filter {
+
+	private final Filter delegate;
+	private final Logger log;
+
+	public TraceableDelegatingFilter(Filter delegate) {
+		Assert.notNull(delegate, "Delegate Filter cannot be null.");
+		this.delegate = delegate;
+		this.log = LoggerFactory.getLogger(delegate.getClass());
+	}
+
+	@Override
+	public <T> List<T> filter(List<T> items, Object key) {
+		if(log.isTraceEnabled()) {
+			log.trace("filtering {} using key {}", items, key);
+		}
+		List<T> l = delegate.filter(items, key);
+		if(log.isTraceEnabled()) {
+			log.trace("items {} matched key {}", (null == items ? Collections.emptyList() : items), key);
+		}
+		return l;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/filter/package-info.java b/reactor-core/src/main/java/reactor/filter/package-info.java
new file mode 100644
index 0000000..8c9ef04
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/filter/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Filters eliminate components from a list based on a predicate.
+ */
+package reactor.filter;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/function/Consumer.java b/reactor-core/src/main/java/reactor/function/Consumer.java
new file mode 100644
index 0000000..3d23ed2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/Consumer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function;
+
+/**
+ * Implementations accept a given value and perform work on the argument.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ *
+ * @param <T> the type of values to accept
+ */
+public interface Consumer<T> {
+
+	/**
+	 * Execute the logic of the action, accepting the given parameter.
+	 *
+	 * @param t The parameter to pass to the consumer.
+	 */
+	void accept(T t);
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/Fn.java b/reactor-core/src/main/java/reactor/function/Fn.java
new file mode 100644
index 0000000..9ca4a67
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/Fn.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function;
+
+import reactor.function.Functions;
+
+/**
+ * Alias for Functions
+ *
+ * @author Stephane Maldini
+ */
+public abstract class Fn extends Functions {
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/function/Function.java b/reactor-core/src/main/java/reactor/function/Function.java
new file mode 100644
index 0000000..df3c588
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/Function.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function;
+
+/**
+ * Implementations of this class perform work on the given parameter and return a result of an optionally different
+ * type.
+ *
+ * @param <T> The type of the input to the apply operation
+ * @param <R> The type of the result of the apply operation
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public interface Function<T, R> {
+
+	/**
+	 * Execute the logic of the action, accepting the given parameter.
+	 *
+	 * @param t The parameter to pass to the action.
+	 *
+	 * @return result
+	 */
+	R apply(T t);
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/Functions.java b/reactor-core/src/main/java/reactor/function/Functions.java
new file mode 100644
index 0000000..2d2482e
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/Functions.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function;
+
+import java.lang.reflect.Constructor;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Helper methods to provide syntax sugar for working with functional components in Reactor.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+public abstract class Functions {
+
+	/**
+	 * Chain a set of {@link Consumer Consumers} together into a single {@link Consumer}.
+	 *
+	 * @param consumers
+	 * 		the {@link Consumer Consumers} to chain together
+	 * @param <T>
+	 * 		type of the event handled by the {@link Consumer}
+	 *
+	 * @return a new {@link Consumer}
+	 */
+	@SafeVarargs
+	public static <T> Consumer<T> chain(Consumer<T>... consumers) {
+		final AtomicReference<Consumer<T>> composition = new AtomicReference<Consumer<T>>();
+		for(final Consumer<T> next : consumers) {
+			if(null == composition.get()) {
+				composition.set(next);
+			} else {
+				composition.set(new Consumer<T>() {
+					final Consumer<T> prev = composition.get();
+
+					public void accept(T t) {
+						prev.accept(t);
+						next.accept(t);
+					}
+				});
+			}
+		}
+		return composition.get();
+	}
+
+	/**
+	 * Wrap the given {@link java.util.concurrent.Callable} and compose a new {@link reactor.function.Function}.
+	 *
+	 * @param c
+	 * 		The {@link java.util.concurrent.Callable}.
+	 *
+	 * @return An {@link reactor.function.Consumer} that executes the {@link java.util.concurrent.Callable}.
+	 */
+	public static <T, V> Function<T, V> function(final Callable<V> c) {
+		return new Function<T, V>() {
+			@Override
+			public V apply(T o) {
+				try {
+					return c.call();
+				} catch(Exception e) {
+					throw new IllegalStateException(e);
+				}
+			}
+		};
+	}
+
+	/**
+	 * Wrap the given {@link Runnable} and compose a new {@link reactor.function.Consumer}.
+	 *
+	 * @param r
+	 * 		The {@link Runnable}.
+	 *
+	 * @return An {@link reactor.function.Consumer} that executes the {@link Runnable}.
+	 */
+	public static <T> Consumer<T> consumer(final Runnable r) {
+		return new Consumer<T>() {
+			@Override
+			public void accept(T t) {
+				r.run();
+			}
+		};
+	}
+
+	/**
+	 * Creates a {@code Supplier} that will always return the given {@code value}.
+	 *
+	 * @param value
+	 * 		the value to be supplied
+	 *
+	 * @return the supplier for the value
+	 */
+	public static <T> Supplier<T> supplier(final T value) {
+		return new Supplier<T>() {
+			@Override
+			public T get() {
+				return value;
+			}
+		};
+	}
+
+	/**
+	 * Creates a {@code Supplier} that will return a new instance of {@code type} each time
+	 * it's called.
+	 *
+	 * @param type
+	 * 		The type to create
+	 *
+	 * @return The supplier that will create instances
+	 *
+	 * @throws IllegalArgumentException
+	 * 		if {@code type} does not have a zero-args constructor
+	 */
+	public static <T> Supplier<T> supplier(final Class<T> type) {
+		try {
+			final Constructor<T> ctor = type.getConstructor();
+			return new Supplier<T>() {
+				@Override
+				public T get() {
+					try {
+						return ctor.newInstance();
+					} catch(Exception e) {
+						throw new IllegalStateException(e.getMessage(), e);
+					}
+				}
+			};
+		} catch(NoSuchMethodException e) {
+			throw new IllegalArgumentException(e.getMessage(), e);
+		}
+	}
+
+	/**
+	 * Creates a {@code Supplier} that will {@link Callable#call call} the {@code callable}
+	 * each time it's asked for a value.
+	 *
+	 * @param callable
+	 * 		The {@link Callable}.
+	 *
+	 * @return A {@link Supplier} that executes the {@link Callable}.
+	 */
+	public static <T> Supplier<T> supplier(final Callable<T> callable) {
+		return new Supplier<T>() {
+			@Override
+			public T get() {
+				try {
+					return callable.call();
+				} catch(Exception e) {
+					throw new IllegalStateException(e);
+				}
+			}
+		};
+	}
+
+	/**
+	 * Creates a {@code Supplier} that will {@link Future#get get} its value from the
+	 * {@code future} each time it's asked for a value.
+	 *
+	 * @param future
+	 * 		The future to get values from
+	 *
+	 * @return A {@link reactor.function.Supplier} that gets its values from the Future
+	 */
+	public static <T> Supplier<T> supplier(final Future<T> future) {
+		return new Supplier<T>() {
+			@Override
+			public T get() {
+				try {
+					return future.get();
+				} catch(Exception e) {
+					throw new IllegalStateException(e);
+				}
+			}
+		};
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/Predicate.java b/reactor-core/src/main/java/reactor/function/Predicate.java
new file mode 100644
index 0000000..9f7c7c6
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/Predicate.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function;
+
+/**
+ * Determines if the input object matches some criteria.
+ *
+ * @param <T>
+ * 		the type of object that the predicate can test
+ *
+ * @author Jon Brisbin
+ */
+public interface Predicate<T> {
+
+	/**
+	 * Returns {@literal true} if the input object matches some criteria.
+	 *
+	 * @param t
+	 * 		The input object.
+	 *
+	 * @return {@literal true} if the criteria matches, {@literal false} otherwise.
+	 */
+	boolean test(T t);
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/Predicates.java b/reactor-core/src/main/java/reactor/function/Predicates.java
new file mode 100644
index 0000000..b4bba54
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/Predicates.java
@@ -0,0 +1,104 @@
+package reactor.function;
+
+import reactor.util.Assert;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class Predicates {
+
+	protected Predicates() {
+	}
+
+	/**
+	 * Returns a {@literal Predicate} which evaluates to {@literal true} only if all the provided predicates evaluate to
+	 * {@literal true}.
+	 *
+	 * @param predicates
+	 * 		{@literal Predicate Predicates} which will be ANDed together.
+	 *
+	 * @return A new {@literal Predicate} which returns {@literal true} only if all {@literal Predicate Predicates}
+	 * return
+	 * {@literal true}.
+	 */
+	public static <T> Predicate<T> and(final Predicate<? super T>... predicates) {
+		Assert.notEmpty(predicates, "Predicate array cannot be empty.");
+		return new Predicate<T>() {
+			@Override
+			public boolean test(T t) {
+				for(Predicate<? super T> p : predicates) {
+					if(!p.test(t)) {
+						return false;
+					}
+				}
+				return true;
+			}
+		};
+	}
+
+	/**
+	 * Returns a {@literal Predicate} which negates the given {@literal Predicate}.
+	 *
+	 * @param p
+	 * 		the {@literal Predicate} to negate
+	 *
+	 * @return A new {@literal Predicate} which is always the opposite of the result of this {@literal Predicate}.
+	 */
+	public static <T> Predicate<T> negate(final Predicate<? super T> p) {
+		return new Predicate<T>() {
+			@Override
+			public boolean test(T t) {
+				return !p.test(t);
+			}
+		};
+	}
+
+	/**
+	 * Returns a {@literal Predicate} which evaluates to {@literal true} if either this predicate or the provided
+	 * predicate
+	 * evaluate to {@literal true}.
+	 *
+	 * @param p1
+	 * 		A {@literal Predicate} which will be ORed together with {@literal p2}.
+	 * @param p2
+	 * 		A {@literal Predicate} which will be ORed together with {@literal p1}.
+	 *
+	 * @return A new {@literal Predicate} which returns {@literal true} if either {@literal Predicate} returns {@literal
+	 * true}.
+	 */
+	public static <T> Predicate<T> or(final Predicate<? super T> p1, final Predicate<? super T> p2) {
+		Assert.notNull(p1, "Predicate 1 cannot be null.");
+		Assert.notNull(p2, "Predicate 2 cannot be null.");
+		return new Predicate<T>() {
+			@Override
+			public boolean test(T t) {
+				return p1.test(t) || p2.test(t);
+			}
+		};
+	}
+
+	/**
+	 * Returns a {@literal Predicate} which evaluates to {@literal true} if either both {@literal Predicate Predicates}
+	 * return {@literal true} or neither of them do.
+	 *
+	 * @param p1
+	 * 		A {@literal Predicate} which will be XORed together with {@literal p2}.
+	 * @param p2
+	 * 		A {@literal Predicate} which will be XORed together with {@literal p1}.
+	 *
+	 * @return A new {@literal Predicate} which returns {@literal true} if both {@literal Predicate Predicates} return
+	 * {@literal true} or neither of them do.
+	 */
+	public static <T> Predicate<T> xor(final Predicate<? super T> p1, final Predicate<? super T> p2) {
+		Assert.notNull(p1, "Predicate 1 cannot be null.");
+		Assert.notNull(p2, "Predicate 2 cannot be null.");
+		return new Predicate<T>() {
+			@Override
+			public boolean test(T t) {
+				return (!p1.test(t) && !p2.test(t))
+						|| (p1.test(t) && p2.test(t));
+			}
+		};
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/Supplier.java b/reactor-core/src/main/java/reactor/function/Supplier.java
new file mode 100644
index 0000000..80bccd2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/Supplier.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function;
+
+/**
+ * Implementations of this class supply the caller with an object. The provided object can be created each call to
+ * {@code get()} or can be created in some other way.
+ *
+ * @param <T> the type of the supplied object
+ *
+ * @author Jon Brisbin
+ */
+public interface Supplier<T> {
+
+	/**
+	 * Get an object.
+	 *
+	 * @return An object.
+	 */
+	T get();
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/Suppliers.java b/reactor-core/src/main/java/reactor/function/Suppliers.java
new file mode 100644
index 0000000..ba285c3
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/Suppliers.java
@@ -0,0 +1,214 @@
+package reactor.function;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper class for working tying {@link Supplier Suppliers} to {@link Iterable Iterables} and other types of
+ * collections.
+ *
+ * @author Jon Brisbin
+ */
+public abstract class Suppliers {
+
+	private Suppliers() {
+	}
+
+	/**
+	 * Wrap the given object that will supply the given object every time {@link reactor.function.Supplier#get()} is
+	 * called.
+	 *
+	 * @param obj
+	 * 		the object to supply
+	 * @param <T>
+	 * 		type of the supplied object
+	 *
+	 * @return the new {@link Supplier}
+	 */
+	public static <T> Supplier<T> supply(final T obj) {
+		return new Supplier<T>() {
+			@Override
+			public T get() {
+				return obj;
+			}
+		};
+	}
+
+	/**
+	 * Supply the given object only once, the first time {@link reactor.function.Supplier#get()} is invoked.
+	 *
+	 * @param obj
+	 * 		the object to supply
+	 * @param <T>
+	 * 		type of the supplied object
+	 *
+	 * @return the new {@link Supplier}
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> Supplier<T> supplyOnce(final T obj) {
+		return drain(Arrays.asList(obj));
+	}
+
+	/**
+	 * Supply the given object to callers only as long as the given {@link reactor.function.Predicate} returns true.
+	 *
+	 * @param obj
+	 * 		the object to supply
+	 * @param predicate
+	 * 		the predicate to check to determine whether or not to supply the given value
+	 * @param <T>
+	 * 		type of the supplied object
+	 *
+	 * @return the new {@link Supplier}
+	 */
+	public static <T> Supplier<T> supplyWhile(final T obj, final Predicate<T> predicate) {
+		return new Supplier<T>() {
+			@Override
+			public T get() {
+				if(predicate.test(obj)) {
+					return obj;
+				} else {
+					return null;
+				}
+			}
+		};
+	}
+
+	/**
+	 * Create a {@link reactor.function.Supplier} that continually round-robin load balances each call to {@link
+	 * Supplier#get()} by iterating over the objects. When the end is reached, it wraps around to the first object and
+	 * keeps providing objects to callers.
+	 *
+	 * @param objs
+	 * 		the objects to load-balance
+	 * @param <T>
+	 * 		type of the supplied object
+	 *
+	 * @return the new {@link Supplier}
+	 */
+	public static <T> Supplier<T> roundRobin(final T... objs) {
+		final AtomicInteger count = new AtomicInteger();
+		final int len = objs.length;
+
+		return new Supplier<T>() {
+			@Override public T get() {
+				return objs[count.getAndIncrement() % len];
+			}
+		};
+	}
+
+	/**
+	 * Filter the given {@link Iterable} using the given {@link Predicate} so that calls to the return {@link
+	 * reactor.function.Supplier#get()} will provide only items from the original collection which pass the predicate
+	 * test.
+	 *
+	 * @param src
+	 * 		the source of objects to filter
+	 * @param predicate
+	 * 		the {@link Predicate} to test items against
+	 * @param <T>
+	 * 		type of the source
+	 *
+	 * @return the new {@link Supplier}
+	 */
+	public static <T> Supplier<T> filter(final Iterable<T> src, final Predicate<T> predicate) {
+		return new Supplier<T>() {
+			Iterator<T> iter = src.iterator();
+
+			@Override
+			public T get() {
+				if(!iter.hasNext()) {
+					return null;
+				}
+				T obj;
+				do {
+					obj = iter.next();
+				} while(!predicate.test(obj));
+				return obj;
+			}
+		};
+	}
+
+	/**
+	 * Create a {@link reactor.function.Supplier} which drains the contents of the given {@link java.lang.Iterable} by
+	 * internally creating an {@link java.util.Iterator} and delegating each call of {@link
+	 * reactor.function.Supplier#get()} to {@link java.util.Iterator#next()}.
+	 *
+	 * @param c
+	 * 		the collection to drain
+	 * @param <T>
+	 * 		type of the source
+	 *
+	 * @return the new {@link Supplier}
+	 */
+	public static <T> Supplier<T> drain(Iterable<T> c) {
+		final Iterator<T> iter = c.iterator();
+
+		return new Supplier<T>() {
+			@Override
+			public T get() {
+				return (iter.hasNext() ? iter.next() : null);
+			}
+		};
+	}
+
+	/**
+	 * Create a {@link reactor.function.Supplier} which drains all of the given {@link java.lang.Iterable Iterables}.
+	 *
+	 * @param iters
+	 * 		the collections to drain
+	 * @param <T>
+	 * 		type of the source
+	 *
+	 * @return the new {@link Supplier}
+	 *
+	 * @see #drain(Iterable)
+	 */
+	public static <T> Supplier<T> drainAll(Iterable<Iterable<T>> iters) {
+		List<Supplier<T>> ls = new ArrayList<Supplier<T>>();
+		for(Iterable<T> iter : iters) {
+			ls.add(drain(iter));
+		}
+		return collect(ls);
+	}
+
+	/**
+	 * Create a {@link reactor.function.Supplier} that aggregates the given list of suppliers by calling each one, in
+	 * turn, until the supplier returns {@code null}. The aggregator then goes on to the next supplier in the list and
+	 * delegates calls to that supplier, and so on, until the end of the list is reached.
+	 *
+	 * @param suppliers
+	 * 		the list of suppliers to delegate to
+	 * @param <T>
+	 * 		type of the source
+	 *
+	 * @return the new {@link Supplier}
+	 */
+	public static <T> Supplier<T> collect(List<Supplier<T>> suppliers) {
+		final ListIterator<Supplier<T>> iter = suppliers.listIterator();
+
+		return new Supplier<T>() {
+			@Override
+			public synchronized T get() {
+				if(iter.hasNext()) {
+					T obj = iter.next().get();
+					if(null != obj) {
+						return obj;
+					}
+				} else if(iter.hasPrevious()) {
+					// rewind
+					while(iter.hasPrevious()) {
+						iter.previous();
+					}
+					return get();
+				}
+				return null;
+			}
+		};
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/batch/BatchConsumer.java b/reactor-core/src/main/java/reactor/function/batch/BatchConsumer.java
new file mode 100644
index 0000000..803aa18
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/batch/BatchConsumer.java
@@ -0,0 +1,22 @@
+package reactor.function.batch;
+
+import reactor.function.Consumer;
+
+/**
+ * {@link reactor.function.Consumer} that is notified of the start and end of a batch.
+ *
+ * @author Jon Brisbin
+ */
+public interface BatchConsumer<T> extends Consumer<T> {
+
+	/**
+	 * Called when a batch begins.
+	 */
+	void start();
+
+	/**
+	 * Called when a batch ends.
+	 */
+	void end();
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/batch/package-info.java b/reactor-core/src/main/java/reactor/function/batch/package-info.java
new file mode 100644
index 0000000..a33e27b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/batch/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Extension of a standard {@link reactor.function.Consumer} that is aware of batch semantics.
+ */
+package reactor.function.batch;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/function/package-info.java b/reactor-core/src/main/java/reactor/function/package-info.java
new file mode 100644
index 0000000..30d1f0f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * This package contains the foundational abstractions on which Reactor's functional and reactive components are
+ * built. It includes common abstractions like {@link reactor.function.Consumer}, {@link reactor.function.Supplier},
+ * {@link reactor.function.Function}, {@link reactor.function.Predicate} and the like,
+ * which are used throughout a Reactor application.
+ */
+package reactor.function;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/function/support/Boundary.java b/reactor-core/src/main/java/reactor/function/support/Boundary.java
new file mode 100644
index 0000000..d80d739
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/support/Boundary.java
@@ -0,0 +1,119 @@
+package reactor.function.support;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import reactor.function.Consumer;
+
+/**
+ * A {@code Boundary} is a blocking utility that allows the user to bind an arbitrary number of {@link Consumer
+ * Consumers} to it. Whenever {@link #bind(reactor.function.Consumer)} is called, it returns a new {@code Consumer}
+ * that
+ * internally creates a {@link CountDownLatch} which calls the delegate {@code Consumer}, then counts down the latch.
+ * Calling {@link #await()} or {@link #await(long, java.util.concurrent.TimeUnit)} on the {@code Boundary} will block
+ * the calling thread until all bound latches are released.
+ * <p/>
+ * The timeout value given is the total timeout value and not the per-latch timeout value. If a timeout of 5 seconds is
+ * specified and there are 100 latches bound, then all 100 latches have a combined 5 seconds to be counted down, not 5
+ * seconds per latch, which would equate to a little under 10 minutes.
+ *
+ * @author Jon Brisbin
+ */
+public class Boundary {
+
+	private final List<CountDownLatch> latches = new ArrayList<CountDownLatch>();
+
+	public Boundary() {
+	}
+
+	/**
+	 * Bind the given {@link Consumer} to this {@code Boundary} by creating a {@link CountDownLatch} that will be counted
+	 * down the first time the given {@link Consumer} is invoked.
+	 *
+	 * @param consumer
+	 * 		The delegate {@code Consumer}
+	 * @param <T>
+	 * 		The type of the value accepted by the {@code Consumer}
+	 *
+	 * @return A new {@code Consumer} which will count down the internal latch after invoking the delegate {@code
+	 * Consumer}
+	 */
+	public <T> Consumer<T> bind(Consumer<T> consumer) {
+		return bind(consumer, 1);
+	}
+
+	/**
+	 * Bind the given {@link Consumer} to this {@code Boundary} by creating a {@link CountDownLatch} of the given size
+	 * that will be counted down the every time the given {@link Consumer} is invoked.
+	 *
+	 * @param consumer
+	 * 		The delegate {@code Consumer}
+	 * @param <T>
+	 * 		The type of the value accepted by the {@code Consumer}
+	 *
+	 * @return A new {@code Consumer} which will count down the internal latch after invoking the delegate {@code
+	 * Consumer}
+	 */
+	public <T> Consumer<T> bind(final Consumer<T> consumer, int expected) {
+		synchronized(latches) {
+			final CountDownLatch latch = new CountDownLatch(expected);
+			latches.add(latch);
+
+			return new Consumer<T>() {
+				@Override
+				public void accept(T t) {
+					consumer.accept(t);
+					latch.countDown();
+				}
+			};
+		}
+	}
+
+	/**
+	 * Wait for all latches to be counted down (almost) indefinitely.
+	 *
+	 * @return {@code true} if all latches were counted down within the timeout value, {@code false} otherwise
+	 */
+	public boolean await() {
+		return await(Integer.MAX_VALUE, TimeUnit.SECONDS);
+	}
+
+	/**
+	 * Wait for all latches to be counted down within the given timeout window.
+	 *
+	 * @param timeout
+	 * 		The timeout value.
+	 * @param timeUnit
+	 * 		The unit of time measured by the timeout value.
+	 *
+	 * @return {@code true} if all latches were counted down within the timeout value, {@code false} otherwise
+	 */
+	public boolean await(long timeout, TimeUnit timeUnit) {
+		if(latches.isEmpty()) {
+			// we're not watching any latches
+			return true;
+		}
+
+		final long start = System.currentTimeMillis();
+		final long timeoutMillis = TimeUnit.MILLISECONDS.convert(timeout, timeUnit);
+		synchronized(latches) {
+			try {
+				long elapsed = 0;
+				for(CountDownLatch latch : latches) {
+					boolean b = latch.await(timeoutMillis - elapsed, TimeUnit.MILLISECONDS);
+					elapsed = System.currentTimeMillis() - start;
+					if(!b || elapsed >= timeoutMillis) {
+						return false;
+					}
+				}
+			} catch(InterruptedException e) {
+				Thread.currentThread().interrupt();
+			}
+		}
+
+		return (System.currentTimeMillis() - start < timeoutMillis);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/support/CancelConsumerException.java b/reactor-core/src/main/java/reactor/function/support/CancelConsumerException.java
new file mode 100644
index 0000000..3f4ccf0
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/support/CancelConsumerException.java
@@ -0,0 +1,21 @@
+package reactor.function.support;
+
+/**
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class CancelConsumerException extends RuntimeException {
+	private static final long serialVersionUID = 5373523865364055930L;
+
+	public CancelConsumerException() {
+	}
+
+	public CancelConsumerException(String message) {
+		super(message);
+	}
+
+	@Override
+	public synchronized Throwable fillInStackTrace() {
+		return this;
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/function/support/DelegatingConsumer.java b/reactor-core/src/main/java/reactor/function/support/DelegatingConsumer.java
new file mode 100644
index 0000000..35ec6d2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/support/DelegatingConsumer.java
@@ -0,0 +1,210 @@
+package reactor.function.support;
+
+import reactor.function.Consumer;
+import reactor.function.batch.BatchConsumer;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An implementation of {@link Consumer} that maintains a list of delegates to which events received by this {@link
+ * Consumer} will be passed along.
+ * <p/>
+ * NOTE: Access to the list of delegates is {@code synchronized} to make it thread-safe, so using this implementation of
+ * {@link Consumer} will incur an overall performance hit on throughput. Also, references to the {@link Consumer
+ * Consumers} are weak. It's not possible to remove a {@link Consumer} without a reference to it, so the {@code
+ * DelegatingConsumer} assumes references to {@link Consumer Consumers} added here have other components with strong
+ * references to them somewhere else.
+ *
+ * @author Jon Brisbin
+ */
+public class DelegatingConsumer<T> implements BatchConsumer<T>, Iterable<Consumer<T>> {
+
+	private final Object                           delegateMonitor = new Object() {
+	};
+	private final List<WeakReference<Consumer<T>>> delegates       = new ArrayList<WeakReference<Consumer<T>>>();
+	private volatile int delegateSize;
+
+	/**
+	 * Add the given {@link Consumer} to the list of delegates.
+	 *
+	 * @param consumer the {@link Consumer} to add
+	 * @return {@literal this}
+	 */
+	public DelegatingConsumer<T> add(Consumer<T> consumer) {
+		Assert.notNull(consumer, "Consumer cannot be null.");
+		synchronized (delegateMonitor) {
+			delegates.add(new WeakReference<Consumer<T>>(consumer));
+			delegateSize = delegates.size();
+		}
+		return this;
+	}
+
+	/**
+	 * Add the given {@link Consumer Consumers} to the list of delegates.
+	 *
+	 * @param consumers the {@link Consumer Consumers} to add
+	 * @return {@literal this}
+	 */
+	public DelegatingConsumer<T> add(Collection<Consumer<T>> consumers) {
+		if (null == consumers || consumers.isEmpty()) {
+			return this;
+		}
+		for (Consumer<T> c : consumers) {
+			add(c);
+		}
+		return this;
+	}
+
+	/**
+	 * Remove the given {@link Consumer} from the list of delegates.
+	 *
+	 * @param consumer
+	 * @return {@literal this}
+	 */
+	public DelegatingConsumer<T> remove(Consumer<T> consumer) {
+		synchronized (delegateMonitor) {
+			for (WeakReference<Consumer<T>> ref : delegates) {
+				if (ref.get() == consumer) {
+					ref.clear();
+					break;
+				}
+			}
+		}
+		return this;
+	}
+
+	/**
+	 * Remove the given {@link Consumer Consumers} from the list of delegates.
+	 *
+	 * @param consumers the {@link Consumer Consumers} to remove
+	 * @return {@literal this}
+	 */
+	public DelegatingConsumer<T> remove(Collection<Consumer<T>> consumers) {
+		if (null == consumers || consumers.isEmpty()) {
+			return this;
+		}
+		for (Consumer<T> c : consumers) {
+			remove(c);
+		}
+		prune();
+		return this;
+	}
+
+	/**
+	 * Remove the references to {@link Consumer Consumers} that have been removed or have gone out of scope and prune the
+	 * size of the list. If many {@link Consumer Consumers} are removed individually, it will increase overall throughput
+	 * to call this method periodically. To maintain as high a throughput as possible, though, its not necessary to call
+	 * this every time a {@link Consumer} is removed.
+	 *
+	 * @return {@literal this}
+	 */
+	public DelegatingConsumer<T> prune() {
+		synchronized (delegateMonitor) {
+			List<WeakReference<Consumer<T>>> delegatesToRemove = new ArrayList<WeakReference<Consumer<T>>>();
+			for (WeakReference<Consumer<T>> ref : delegates) {
+				if (null == ref.get()) {
+					delegatesToRemove.add(ref);
+				}
+			}
+			delegates.removeAll(delegatesToRemove);
+			delegateSize = delegates.size();
+		}
+		return this;
+	}
+
+	/**
+	 * Clear all delegate {@link Consumer Consumers} from the list.
+	 *
+	 * @return {@literal this}
+	 */
+	public DelegatingConsumer<T> clear() {
+		synchronized (delegateMonitor) {
+			delegates.clear();
+			delegateSize = delegates.size();
+		}
+		return this;
+	}
+
+	@Override
+	public void accept(T t) {
+		synchronized (delegateMonitor) {
+			final int size = delegateSize;
+			for (int i = 0; i < size; i++) {
+				WeakReference<Consumer<T>> ref = delegates.get(i);
+				if (null == ref) {
+					continue;
+				}
+				Consumer<T> c = ref.get();
+				if (null == c) {
+					continue;
+				}
+				c.accept(t);
+			}
+		}
+	}
+
+	@Override
+	@Nonnull
+	public Iterator<Consumer<T>> iterator() {
+		return new Iterator<Consumer<T>>() {
+			final Iterator<WeakReference<Consumer<T>>> delegatesIter = delegates.iterator();
+
+			public boolean hasNext() {
+				return delegatesIter.hasNext();
+			}
+
+			@Override
+			public Consumer<T> next() {
+				return delegatesIter.next().get();
+			}
+
+			@Override
+			public void remove() {
+				delegatesIter.remove();
+			}
+		};
+	}
+
+  @Override
+  public void start() {
+    synchronized (delegateMonitor) {
+      final int size = delegateSize;
+      for (int i = 0; i < size; i++) {
+        WeakReference<Consumer<T>> ref = delegates.get(i);
+        if (null == ref) {
+          continue;
+        }
+        Consumer<T> c = ref.get();
+        if (null == c || !(c instanceof BatchConsumer)) {
+          continue;
+        }
+        ((BatchConsumer) c).start();
+      }
+    }
+  }
+
+  @Override
+  public void end() {
+    synchronized (delegateMonitor) {
+      final int size = delegateSize;
+      for (int i = 0; i < size; i++) {
+        WeakReference<Consumer<T>> ref = delegates.get(i);
+        if (null == ref) {
+          continue;
+        }
+        Consumer<T> c = ref.get();
+        if (null == c || !(c instanceof BatchConsumer)) {
+          continue;
+        }
+        ((BatchConsumer) c).end();
+      }
+    }
+  }
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/support/Poller.java b/reactor-core/src/main/java/reactor/function/support/Poller.java
new file mode 100644
index 0000000..f8ad7e7
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/support/Poller.java
@@ -0,0 +1,124 @@
+package reactor.function.support;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+import reactor.support.NamedDaemonThreadFactory;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * A {@code Poller} funnels non-null values retrieved from a {@link reactor.function.Supplier} to the given {@link
+ * reactor.function.Consumer} using a separate thread created for each instance of a {@code Poller}.
+ *
+ * @author Jon Brisbin
+ */
+public class Poller<T> {
+
+	private static final Logger LOG = LoggerFactory.getLogger(Poller.class);
+
+	private final ExecutorService threadPool = Executors.newSingleThreadExecutor(new NamedDaemonThreadFactory("pipe"));
+	private final Runnable        reader     = new Runnable() {
+		@Override
+		public void run() {
+			while(active) {
+				T obj = supplier.get();
+				if(null != obj) {
+					consumer.accept(obj);
+				} else {
+					try {
+						Thread.sleep(100);
+					} catch(InterruptedException e) {
+						Thread.currentThread().interrupt();
+					}
+				}
+			}
+		}
+	};
+	private final    Supplier<T> supplier;
+	private final    Consumer<T> consumer;
+	private volatile boolean     active;
+	private volatile Future<?>   readerFuture;
+
+	/**
+	 * Poll the given {@link reactor.function.Supplier} for non-null values and pass them to the given {@link
+	 * reactor.function.Consumer}.
+	 *
+	 * @param supplier
+	 * 		the {@link reactor.function.Supplier} to poll
+	 * @param consumer
+	 * 		the {@link reactor.function.Consumer} to pass values to
+	 */
+	public Poller(Supplier<T> supplier, Consumer<T> consumer) {
+		this(supplier, consumer, false);
+	}
+
+	/**
+	 * Poll the given {@link reactor.function.Supplier} for non-null values and pass them to the given {@link
+	 * reactor.function.Consumer}. If {@code paused} is {@code false}, then don't start polling values from the supplier
+	 * until {@link #resume()} is invoked.
+	 *
+	 * @param supplier
+	 * 		the {@link reactor.function.Supplier} to poll
+	 * @param consumer
+	 * 		the {@link reactor.function.Consumer} to pass values to
+	 * @param paused
+	 * 		whether to start this {@code Poller} in a paused state or not
+	 */
+	public Poller(Supplier<T> supplier, Consumer<T> consumer, boolean paused) {
+		this.supplier = supplier;
+		this.consumer = consumer;
+		if(!paused) {
+			resume();
+		}
+	}
+
+	/**
+	 * Resume polling values from the {@link reactor.function.Supplier}.
+	 *
+	 * @return {@code this}
+	 */
+	public synchronized Poller<T> resume() {
+		if(threadPool.isShutdown() || threadPool.isTerminated()) {
+			throw new IllegalStateException("Poller has been shutdown.");
+		}
+		if(null != readerFuture) {
+			// already started
+			return this;
+		}
+		active = true;
+		readerFuture = threadPool.submit(reader);
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("Started pipe from " + supplier + " to " + consumer);
+		}
+		return this;
+	}
+
+	/**
+	 * Pausing polling for values from this {@link reactor.function.Supplier}.
+	 *
+	 * @return {@code this}
+	 */
+	public synchronized Poller<T> pause() {
+		active = false;
+		if(null != readerFuture) {
+			readerFuture.cancel(true);
+			readerFuture = null;
+		}
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("Stopped pipe from " + supplier + " to " + consumer);
+		}
+		return this;
+	}
+
+	/**
+	 * Shutdown this {@code Poller} and stop the internal thread from polling the supplier.
+	 */
+	public void shutdown() {
+		threadPool.shutdown();
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/support/Resequencer.java b/reactor-core/src/main/java/reactor/function/support/Resequencer.java
new file mode 100644
index 0000000..bbd5c41
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/support/Resequencer.java
@@ -0,0 +1,94 @@
+package reactor.function.support;
+
+import reactor.function.Consumer;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A {@code Resequencer} allows claimants to ensure proper ordering of replies by allocating {@code long} values from a
+ * counter. When the claimant is ready to publish the results of the operation, it calls {@link #accept(long, Object)},
+ * passing the slot number it claimed in addition to the value being published. The {@code Resequencer} will ensure
+ * that out-of-order replies are re-ordered by the claimed slot number and later replies are queued and only passed to
+ * the configured {@link reactor.function.Consumer} once the earlier replies have been published.
+ *
+ * @author Jon Brisbin
+ */
+public class Resequencer<T> {
+
+	private final ReentrantLock lock    = new ReentrantLock();
+	private final AtomicLong    slots   = new AtomicLong();
+	private final AtomicLong    claims  = new AtomicLong();
+	private final Map<Long, T>  results = new TreeMap<Long, T>();
+	private final Consumer<T> delegate;
+	private final long        maxBacklog;
+
+	/**
+	 * Create a {@code Resequencer} that delegates to the given {@link reactor.function.Consumer}.
+	 *
+	 * @param delegate
+	 * 		the {@link reactor.function.Consumer} to delegate values to.
+	 */
+	public Resequencer(@Nonnull Consumer<T> delegate) {
+		this(delegate, Integer.MAX_VALUE);
+	}
+
+	/**
+	 * Create a {@code Resequencer} that delegates to the given {@link reactor.function.Consumer}. Only queue {@code
+	 * maxBacklog} number of items before throwing an exception.
+	 *
+	 * @param delegate
+	 * 		the {@link reactor.function.Consumer} to delegate values to.
+	 * @param maxBacklog
+	 * 		the maximum number of items to queue in the backlog waiting on an earlier reply.
+	 */
+	public Resequencer(@Nonnull Consumer<T> delegate, long maxBacklog) {
+		this.delegate = delegate;
+		this.maxBacklog = maxBacklog;
+	}
+
+	/**
+	 * Accept and possibly queue a value for the given {@code slot}.
+	 *
+	 * @param slot
+	 * 		the slot id this value is a reply for.
+	 * @param t
+	 * 		the value to publish.
+	 */
+	public void accept(long slot, T t) {
+		lock.lock();
+		try {
+			Assert.notNull(slot, "Slot cannot be null.");
+			Assert.isTrue(slot <= slots.get(),
+			              "Cannot accept a value for slot " + slot + " when only " + slots.get() + " slots have been " +
+					              "allocated.");
+
+			long next = claims.get() + 1;
+			if(slot == next) {
+				delegate.accept(t);
+				claims.incrementAndGet();
+				if(!results.isEmpty()) {
+					for(Map.Entry<Long, T> entry : results.entrySet()) {
+						delegate.accept(entry.getValue());
+						claims.incrementAndGet();
+					}
+					results.clear();
+				}
+			} else {
+				Assert.isTrue(slot - claims.get() < maxBacklog, "Cannot backlog more than " + maxBacklog + " items.");
+				results.put(slot, t);
+			}
+		} finally {
+			lock.unlock();
+		}
+	}
+
+	public long next() {
+		return slots.incrementAndGet();
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/support/SingleUseConsumer.java b/reactor-core/src/main/java/reactor/function/support/SingleUseConsumer.java
new file mode 100644
index 0000000..f34a0ca
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/support/SingleUseConsumer.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function.support;
+
+import reactor.event.registry.Registration;
+import reactor.function.Consumer;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A {@link Consumer} implementation that allows the delegate {@link Consumer} to only be called once. Should be used in
+ * combination with {@link Registration#cancelAfterUse()} to ensure that this {@link reactor.function.Consumer
+ * Consumer's} {@link reactor.event.registry.Registration} is cancelled as soon after its use as possible.
+ *
+ * @param <T>
+ * 		the type of the values that the consumer can accept
+ *
+ * @author Jon Brisbin
+ */
+public class SingleUseConsumer<T> implements Consumer<T> {
+
+	private final AtomicBoolean called = new AtomicBoolean();
+
+	private final Consumer<T> delegate;
+
+	/**
+	 * Used to create anonymous subclasses.
+	 */
+	public SingleUseConsumer() {
+		this.delegate = null;
+	}
+
+	/**
+	 * Create a single-use {@link Consumer} using the given delgate.
+	 *
+	 * @param delegate
+	 * 		The {@link Consumer} to delegate accept calls to.
+	 */
+	public SingleUseConsumer(Consumer<T> delegate) {
+		this.delegate = delegate;
+	}
+
+	/**
+	 * Static helper method for creating {@code SingleUseConsumer SingleUseConsumers} in code with a little less noise.
+	 *
+	 * @param delegate
+	 * 		The delegate {@code Consumer} to invoke.
+	 * @param <T>
+	 * 		Type of the Consumer's argument.
+	 *
+	 * @return A new {@code Consumer} that will only be invoked once, then cancelled.
+	 */
+	public static <T> Consumer<T> once(Consumer<T> delegate) {
+		return new SingleUseConsumer<T>(delegate);
+	}
+
+	@Override
+	public final void accept(T t) {
+		if (called.get()) {
+			return;
+		}
+
+		if (called.compareAndSet(false, true)) {
+			if (null != delegate) {
+				delegate.accept(t);
+			} else {
+				doAccept(t);
+			}
+			throw new CancelConsumerException();
+		}
+	}
+
+	protected void doAccept(T t) {
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/support/Tap.java b/reactor-core/src/main/java/reactor/function/support/Tap.java
new file mode 100644
index 0000000..e11c862
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/support/Tap.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.function.support;
+
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A {@code Tap} provides a limited window into an event stream. Using a {@code Tap} one can
+ * inspect the current event passing through a stream. A {@code Tap}'s value will be
+ * continually updated as data passes through the stream, so a call to {@link #get()} will
+ * return the last value seen by the event stream.
+ *
+ * @param <T> the type of values that this Tap can consume and supply
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ */
+public class Tap<T> implements Consumer<T>, Supplier<T> {
+
+	private final AtomicReference<T> value = new AtomicReference<T>();
+
+	/**
+	 * Create a {@code Tap}.
+	 */
+	public Tap() {
+	}
+
+	/**
+	 * Get the value of this {@code Tap}, which is the current value of the event stream this
+	 * tap is consuming.
+	 *
+	 * @return the value
+	 */
+	@Override
+	public T get() {
+		return value.get();
+	}
+
+	@Override
+	public void accept(T value) {
+		this.value.set(value);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/function/support/UriUtils.java b/reactor-core/src/main/java/reactor/function/support/UriUtils.java
new file mode 100644
index 0000000..49bd893
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/function/support/UriUtils.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function.support;
+
+import reactor.util.Assert;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.BitSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility class for URI encoding and decoding based on RFC 3986. Offers encoding methods for
+ * the various URI components.
+ * <p/>
+ * All {@code encode*(String, String} methods in this class operate in a similar way:
+ * <ul>
+ *   <li>Valid characters for the specific URI component as defined in RFC 3986 stay the same.
+ *   <li>All other characters are converted into one or more bytes in the given encoding scheme.
+ *       Each of the resulting bytes is written as a hexadecimal string in the
+ *       "<code>%<i>xy</i></code>" format.
+ * </ul>
+ *
+ * @author Arjen Poutsma
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>
+ */
+public abstract class UriUtils {
+
+	private static final BitSet SCHEME;
+
+	private static final BitSet USER_INFO;
+
+	private static final BitSet HOST;
+
+	private static final BitSet PORT;
+
+	private static final BitSet PATH;
+
+	private static final BitSet SEGMENT;
+
+	private static final BitSet QUERY;
+
+	private static final BitSet QUERY_PARAM;
+
+	private static final BitSet FRAGMENT;
+
+	private static final String SCHEME_PATTERN = "([^:/?#]+):";
+
+	private static final String HTTP_PATTERN = "(http|https):";
+
+	private static final String USERINFO_PATTERN = "([^@/]*)";
+
+	private static final String HOST_PATTERN = "([^/?#:]*)";
+
+	private static final String PORT_PATTERN = "(\\d*)";
+
+	private static final String PATH_PATTERN = "([^?#]*)";
+
+	private static final String QUERY_PATTERN = "([^#]*)";
+
+	private static final String LAST_PATTERN = "(.*)";
+
+	// Regex patterns that matches URIs. See RFC 3986, appendix B
+	private static final Pattern URI_PATTERN = Pattern.compile(
+			"^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
+					")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");
+
+	private static final Pattern HTTP_URL_PATTERN = Pattern.compile(
+			"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" +
+					")?" + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
+
+
+	static {
+		// variable names refer to RFC 3986, appendix A
+		BitSet alpha = new BitSet(256);
+		for (int i = 'a'; i <= 'z'; i++) {
+			alpha.set(i);
+		}
+		for (int i = 'A'; i <= 'Z'; i++) {
+			alpha.set(i);
+		}
+		BitSet digit = new BitSet(256);
+		for (int i = '0'; i <= '9'; i++) {
+			digit.set(i);
+		}
+
+		BitSet gendelims = new BitSet(256);
+		gendelims.set(':');
+		gendelims.set('/');
+		gendelims.set('?');
+		gendelims.set('#');
+		gendelims.set('[');
+		gendelims.set(']');
+		gendelims.set('@');
+
+		BitSet subdelims = new BitSet(256);
+		subdelims.set('!');
+		subdelims.set('$');
+		subdelims.set('&');
+		subdelims.set('\'');
+		subdelims.set('(');
+		subdelims.set(')');
+		subdelims.set('*');
+		subdelims.set('+');
+		subdelims.set(',');
+		subdelims.set(';');
+		subdelims.set('=');
+
+		BitSet reserved = new BitSet(256);
+		reserved.or(gendelims);
+		reserved.or(subdelims);
+
+		BitSet unreserved = new BitSet(256);
+		unreserved.or(alpha);
+		unreserved.or(digit);
+		unreserved.set('-');
+		unreserved.set('.');
+		unreserved.set('_');
+		unreserved.set('~');
+
+		SCHEME = new BitSet(256);
+		SCHEME.or(alpha);
+		SCHEME.or(digit);
+		SCHEME.set('+');
+		SCHEME.set('-');
+		SCHEME.set('.');
+
+		USER_INFO = new BitSet(256);
+		USER_INFO.or(unreserved);
+		USER_INFO.or(subdelims);
+		USER_INFO.set(':');
+
+		HOST = new BitSet(256);
+		HOST.or(unreserved);
+		HOST.or(subdelims);
+
+		PORT = new BitSet(256);
+		PORT.or(digit);
+
+		BitSet pchar = new BitSet(256);
+		pchar.or(unreserved);
+		pchar.or(subdelims);
+		pchar.set(':');
+		pchar.set('@');
+
+		SEGMENT = new BitSet(256);
+		SEGMENT.or(pchar);
+
+		PATH = new BitSet(256);
+		PATH.or(SEGMENT);
+		PATH.set('/');
+
+		QUERY = new BitSet(256);
+		QUERY.or(pchar);
+		QUERY.set('/');
+		QUERY.set('?');
+
+		QUERY_PARAM = new BitSet(256);
+		QUERY_PARAM.or(pchar);
+		QUERY_PARAM.set('/');
+		QUERY_PARAM.set('?');
+		QUERY_PARAM.clear('=');
+		QUERY_PARAM.clear('+');
+		QUERY_PARAM.clear('&');
+
+		FRAGMENT = new BitSet(256);
+		FRAGMENT.or(pchar);
+		FRAGMENT.set('/');
+		FRAGMENT.set('?');
+	}
+
+
+	/**
+	 * Encodes the given source URI into an encoded String. All various URI components are encoded according to their
+	 * respective valid character sets.
+	 *
+	 * @param uri      the URI to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded URI
+	 * @throws IllegalArgumentException     when the given uri parameter is not a valid URI
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeUri(String uri, String encoding) throws UnsupportedEncodingException {
+		Assert.notNull(uri, "'uri' must not be null");
+		Assert.notNull(encoding, "'encoding' must not be empty");
+		Matcher m = URI_PATTERN.matcher(uri);
+		if (m.matches()) {
+			String scheme = m.group(2);
+			String authority = m.group(3);
+			String userinfo = m.group(5);
+			String host = m.group(6);
+			String port = m.group(8);
+			String path = m.group(9);
+			String query = m.group(11);
+			String fragment = m.group(13);
+
+			return encodeUriComponents(scheme, authority, userinfo, host, port, path, query, fragment, encoding);
+		} else {
+			throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
+		}
+	}
+
+	/**
+	 * Encodes the given HTTP URI into an encoded String. All various URI components are encoded according to their
+	 * respective valid character sets. <p><strong>Note</strong> that this method does not support fragments ({@code #}),
+	 * as these are not supposed to be sent to the server, but retained by the client.
+	 *
+	 * @param httpUrl  the HTTP URL to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded URL
+	 * @throws IllegalArgumentException     when the given uri parameter is not a valid URI
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeHttpUrl(String httpUrl, String encoding) throws UnsupportedEncodingException {
+		Assert.notNull(httpUrl, "'httpUrl' must not be null");
+		Assert.notNull(encoding, "'encoding' must not be empty");
+		Matcher m = HTTP_URL_PATTERN.matcher(httpUrl);
+		if (m.matches()) {
+			String scheme = m.group(1);
+			String authority = m.group(2);
+			String userinfo = m.group(4);
+			String host = m.group(5);
+			String portString = m.group(7);
+			String path = m.group(8);
+			String query = m.group(10);
+
+			return encodeUriComponents(scheme, authority, userinfo, host, portString, path, query, null, encoding);
+		} else {
+			throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
+		}
+	}
+
+	/**
+	 * Encodes the given source URI components into an encoded String. All various URI components are optional, but encoded
+	 * according to their respective valid character sets.
+	 *
+	 * @param scheme    the scheme
+	 * @param authority the authority
+	 * @param userinfo  the user info
+	 * @param host      the host
+	 * @param port      the port
+	 * @param path      the path
+	 * @param query     the query
+	 * @param fragment  the fragment
+	 * @param encoding  the character encoding to encode to
+	 * @return the encoded URI
+	 * @throws IllegalArgumentException     when the given uri parameter is not a valid URI
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeUriComponents(String scheme,
+																					 String authority,
+																					 String userinfo,
+																					 String host,
+																					 String port,
+																					 String path,
+																					 String query,
+																					 String fragment,
+																					 String encoding)
+			throws UnsupportedEncodingException {
+
+		Assert.notNull(encoding, "'encoding' must not be empty");
+		StringBuilder sb = new StringBuilder();
+
+		if (scheme != null) {
+			sb.append(encodeScheme(scheme, encoding));
+			sb.append(':');
+		}
+
+		if (authority != null) {
+			sb.append("//");
+			if (userinfo != null) {
+				sb.append(encodeUserInfo(userinfo, encoding));
+				sb.append('@');
+			}
+			if (host != null) {
+				sb.append(encodeHost(host, encoding));
+			}
+			if (port != null) {
+				sb.append(':');
+				sb.append(encodePort(port, encoding));
+			}
+		}
+
+		sb.append(encodePath(path, encoding));
+
+		if (query != null) {
+			sb.append('?');
+			sb.append(encodeQuery(query, encoding));
+		}
+
+		if (fragment != null) {
+			sb.append('#');
+			sb.append(encodeFragment(fragment, encoding));
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * Encodes the given URI scheme.
+	 *
+	 * @param scheme   the scheme to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded scheme
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeScheme(String scheme, String encoding) throws UnsupportedEncodingException {
+		return encode(scheme, encoding, SCHEME);
+	}
+
+	/**
+	 * Encodes the given URI user info.
+	 *
+	 * @param userInfo the user info to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded user info
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeUserInfo(String userInfo, String encoding) throws UnsupportedEncodingException {
+		return encode(userInfo, encoding, USER_INFO);
+	}
+
+	/**
+	 * Encodes the given URI host.
+	 *
+	 * @param host     the host to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded host
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeHost(String host, String encoding) throws UnsupportedEncodingException {
+		return encode(host, encoding, HOST);
+	}
+
+	/**
+	 * Encodes the given URI port.
+	 *
+	 * @param port     the port to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded port
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodePort(String port, String encoding) throws UnsupportedEncodingException {
+		return encode(port, encoding, PORT);
+	}
+
+	/**
+	 * Encodes the given URI path.
+	 *
+	 * @param path     the path to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded path
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodePath(String path, String encoding) throws UnsupportedEncodingException {
+		return encode(path, encoding, PATH);
+	}
+
+	/**
+	 * Encodes the given URI path segment.
+	 *
+	 * @param segment  the segment to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded segment
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodePathSegment(String segment, String encoding) throws UnsupportedEncodingException {
+		return encode(segment, encoding, SEGMENT);
+	}
+
+	/**
+	 * Encodes the given URI query.
+	 *
+	 * @param query    the query to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded query
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeQuery(String query, String encoding) throws UnsupportedEncodingException {
+		return encode(query, encoding, QUERY);
+	}
+
+	/**
+	 * Encodes the given URI query parameter.
+	 *
+	 * @param queryParam the query parameter to be encoded
+	 * @param encoding   the character encoding to encode to
+	 * @return the encoded query parameter
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeQueryParam(String queryParam, String encoding) throws UnsupportedEncodingException {
+		return encode(queryParam, encoding, QUERY_PARAM);
+	}
+
+	/**
+	 * Encodes the given URI fragment.
+	 *
+	 * @param fragment the fragment to be encoded
+	 * @param encoding the character encoding to encode to
+	 * @return the encoded fragment
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 */
+	public static String encodeFragment(String fragment, String encoding) throws UnsupportedEncodingException {
+		return encode(fragment, encoding, FRAGMENT);
+	}
+
+	private static String encode(String source, String encoding, BitSet notEncoded)
+			throws UnsupportedEncodingException {
+
+		Assert.notNull(source, "'source' must not be null");
+		Assert.notNull(encoding, "'encoding' must not be empty");
+
+		byte[] bytes = encode(source.getBytes(encoding), notEncoded);
+		return new String(bytes, "US-ASCII");
+	}
+
+	private static byte[] encode(byte[] source, BitSet notEncoded) {
+		Assert.notNull(source, "'source' must not be null");
+		ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length * 2);
+		for (int i = 0; i < source.length; i++) {
+			int b = source[i];
+			if (b < 0) {
+				b += 256;
+			}
+			if (notEncoded.get(b)) {
+				bos.write(b);
+			} else {
+				bos.write('%');
+				char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
+				char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
+				bos.write(hex1);
+				bos.write(hex2);
+			}
+		}
+		return bos.toByteArray();
+	}
+
+	/**
+	 * Decodes the given encoded source String into an URI. Based on the following rules: <ul> <li>Alphanumeric characters
+	 * {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and {@code "0"} through {@code "9"} stay the same.
+	 * <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same. <li>All other
+	 * characters are converted into one or more bytes using the given encoding scheme. Each of the resulting bytes is
+	 * written as a hexadecimal string in the {@code %xy} format. <li>A sequence "<code>%<i>xy</i></code>" is interpreted
+	 * as a hexadecimal representation of the character. </ul>
+	 *
+	 * @param source   the source string
+	 * @param encoding the encoding
+	 * @return the decoded URI
+	 * @throws UnsupportedEncodingException when the given encoding parameter is not supported
+	 * @see java.net.URLDecoder#decode(String, String)
+	 */
+	public static String decode(String source, String encoding) throws UnsupportedEncodingException {
+		Assert.notNull(source, "'source' must not be null");
+		Assert.notNull(encoding, "'encoding' must not be empty");
+		int length = source.length();
+		ByteArrayOutputStream bos = new ByteArrayOutputStream(length);
+		boolean changed = false;
+		for (int i = 0; i < length; i++) {
+			int ch = source.charAt(i);
+			if (ch == '%') {
+				if ((i + 2) < length) {
+					char hex1 = source.charAt(i + 1);
+					char hex2 = source.charAt(i + 2);
+					int u = Character.digit(hex1, 16);
+					int l = Character.digit(hex2, 16);
+					bos.write((char) ((u << 4) + l));
+					i += 2;
+					changed = true;
+				} else {
+					throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
+				}
+			} else {
+				bos.write(ch);
+			}
+		}
+		return changed ? new String(bos.toByteArray(), encoding) : source;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/Buffer.java b/reactor-core/src/main/java/reactor/io/Buffer.java
new file mode 100644
index 0000000..a2bf3a2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/Buffer.java
@@ -0,0 +1,1534 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io;
+
+import reactor.function.Supplier;
+import reactor.alloc.Recyclable;
+import reactor.util.Assert;
+
+import javax.annotation.concurrent.NotThreadSafe;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A {@literal Buffer} is a general-purpose IO utility class that wraps a {@link ByteBuffer}. It provides optional
+ * dynamic expansion of the buffer to accommodate additional content. It also provides convenience methods for
+ * operating
+ * on buffers.
+ *
+ * @author Jon Brisbin
+ */
+ at NotThreadSafe
+public class Buffer implements Recyclable,
+                               Comparable<Buffer>,
+                               Iterable<Byte>,
+                               ReadableByteChannel,
+                               WritableByteChannel {
+
+	/**
+	 * The size, in bytes, of a small buffer. Can be configured using the {@code reactor.io.defaultBufferSize} system
+	 * property. Default to 16384 bytes.
+	 */
+	public static int SMALL_BUFFER_SIZE = Integer.parseInt(
+			System.getProperty("reactor.io.defaultBufferSize", "" + 1024 * 16)
+	);
+
+	/**
+	 * The maximum allowed buffer size in bytes. Can be configured using the {@code reactor.io.maxBufferSize} system
+	 * property. Defaults to 16384000 bytes.
+	 */
+	public static int MAX_BUFFER_SIZE = Integer.parseInt(
+			System.getProperty("reactor.io.maxBufferSize", "" + 1024 * 1000 * 16)
+	);
+
+	private static final Charset UTF8 = Charset.forName("UTF-8");
+	private final boolean        dynamic;
+	private       ByteBuffer     buffer;
+	private       CharsetDecoder decoder;
+	private       CharBuffer     chars;
+	private       int            position;
+	private       int            limit;
+
+	/**
+	 * Create an empty {@literal Buffer} that is dynamic.
+	 */
+	public Buffer() {
+		this.dynamic = true;
+	}
+
+	/**
+	 * Create an {@literal Buffer} that has an internal {@link ByteBuffer} allocated to the given size and optional make
+	 * this buffer fixed-length.
+	 *
+	 * @param atLeast
+	 * 		Allocate this many bytes immediately.
+	 * @param fixed
+	 * 		{@literal true} to make this buffer fixed-length, {@literal false} otherwise.
+	 */
+	public Buffer(int atLeast, boolean fixed) {
+		if(fixed) {
+			if(atLeast <= MAX_BUFFER_SIZE) {
+				this.buffer = ByteBuffer.allocate(atLeast);
+			} else {
+				throw new IllegalArgumentException("Requested buffer size exceeds maximum allowed (" + MAX_BUFFER_SIZE + ")");
+			}
+		} else {
+			ensureCapacity(atLeast);
+		}
+		this.dynamic = !fixed;
+	}
+
+	/**
+	 * Copy constructor that creates a shallow copy of the given {@literal Buffer} by calling {@link
+	 * java.nio.ByteBuffer#duplicate()} on the underlying {@link ByteBuffer}.
+	 *
+	 * @param bufferToCopy
+	 * 		The {@literal Buffer} to copy.
+	 */
+	public Buffer(Buffer bufferToCopy) {
+		this.dynamic = bufferToCopy.dynamic;
+		this.buffer = bufferToCopy.buffer.duplicate();
+	}
+
+	/**
+	 * Create a {@literal Buffer} using the given {@link ByteBuffer} as the inital source.
+	 *
+	 * @param bufferToStartWith
+	 * 		The {@link ByteBuffer} to start with.
+	 */
+	public Buffer(ByteBuffer bufferToStartWith) {
+		this.dynamic = true;
+		this.buffer = bufferToStartWith;
+	}
+
+	/**
+	 * Convenience method to create a new, fixed-length {@literal Buffer} and putting the given byte array into the
+	 * buffer.
+	 *
+	 * @param bytes
+	 * 		The bytes to create a buffer from.
+	 *
+	 * @return The new {@literal Buffer}.
+	 */
+	@SuppressWarnings("resource")
+	public static Buffer wrap(byte[] bytes) {
+		return new Buffer(bytes.length, true)
+				.append(bytes)
+				.flip();
+	}
+
+	/**
+	 * Convenience method to create a new {@literal Buffer} from the given String and optionally specify whether the new
+	 * {@literal Buffer} should be a fixed length or not.
+	 *
+	 * @param str
+	 * 		The String to create a buffer from.
+	 * @param fixed
+	 * 		{@literal true} to create a fixed-length {@literal Buffer}, {@literal false} otherwise.
+	 *
+	 * @return The new {@literal Buffer}.
+	 */
+	@SuppressWarnings("resource")
+	public static Buffer wrap(String str, boolean fixed) {
+		return new Buffer(str.length(), fixed)
+				.append(str)
+				.flip();
+	}
+
+	/**
+	 * Convenience method to create a new, fixed-length {@literal Buffer} from the given String.
+	 *
+	 * @param str
+	 * 		The String to create a buffer from.
+	 *
+	 * @return The new fixed-length {@literal Buffer}.
+	 */
+	public static Buffer wrap(String str) {
+		return wrap(str, true);
+	}
+
+	/**
+	 * Very efficient method for parsing an {@link Integer} from the given {@literal Buffer} range. Much faster than
+	 * {@link Integer#parseInt(String)}.
+	 *
+	 * @param b
+	 * 		The {@literal Buffer} to slice.
+	 * @param start
+	 * 		start of the range.
+	 * @param end
+	 * 		end of the range.
+	 *
+	 * @return The int value or {@literal null} if the {@literal Buffer} could not be read.
+	 */
+	public static Integer parseInt(Buffer b, int start, int end) {
+		b.snapshot();
+
+		b.buffer.limit(end);
+		b.buffer.position(start);
+
+		Integer i = parseInt(b);
+
+		b.reset();
+
+		return i;
+	}
+
+	/**
+	 * Very efficient method for parsing an {@link Integer} from the given {@literal Buffer}. Much faster than {@link
+	 * Integer#parseInt(String)}.
+	 *
+	 * @param b
+	 * 		The {@literal Buffer} to slice.
+	 *
+	 * @return The int value or {@literal null} if the {@literal Buffer} could not be read.
+	 */
+	public static Integer parseInt(Buffer b) {
+		if(b.remaining() == 0) {
+			return null;
+		}
+
+		b.snapshot();
+		int len = b.remaining();
+
+		int num = 0;
+		int dec = 1;
+		for(int i = (b.position + len); i > b.position; ) {
+			char c = (char)b.buffer.get(--i);
+			num += Character.getNumericValue(c) * dec;
+			dec *= 10;
+		}
+
+		b.reset();
+
+		return num;
+	}
+
+	/**
+	 * Very efficient method for parsing a {@link Long} from the given {@literal Buffer} range. Much faster than {@link
+	 * Long#parseLong(String)}.
+	 *
+	 * @param b
+	 * 		The {@literal Buffer} to slice.
+	 * @param start
+	 * 		start of the range.
+	 * @param end
+	 * 		end of the range.
+	 *
+	 * @return The long value or {@literal null} if the {@literal Buffer} could not be read.
+	 */
+	public static Long parseLong(Buffer b, int start, int end) {
+		int origPos = b.buffer.position();
+		int origLimit = b.buffer.limit();
+
+		b.buffer.position(start);
+		b.buffer.limit(end);
+
+		Long l = parseLong(b);
+
+		b.buffer.position(origPos);
+		b.buffer.limit(origLimit);
+
+		return l;
+	}
+
+	/**
+	 * Very efficient method for parsing a {@link Long} from the given {@literal Buffer}. Much faster than {@link
+	 * Long#parseLong(String)}.
+	 *
+	 * @param b
+	 * 		The {@literal Buffer} to slice.
+	 *
+	 * @return The long value or {@literal null} if the {@literal Buffer} could not be read.
+	 */
+	public static Long parseLong(Buffer b) {
+		if(b.remaining() == 0) {
+			return null;
+		}
+		ByteBuffer bb = b.buffer;
+		int origPos = bb.position();
+		int len = bb.remaining();
+
+		long num = 0;
+		int dec = 1;
+		for(int i = len; i > 0; ) {
+			char c = (char)bb.get(--i);
+			num += Character.getNumericValue(c) * dec;
+			dec *= 10;
+		}
+
+		bb.position(origPos);
+
+		return num;
+	}
+
+	@Override
+	public void recycle() {
+		if(null != buffer) {
+			buffer.position(0);
+			position = 0;
+			limit = buffer.capacity();
+			buffer.limit(limit);
+		}
+	}
+
+	/**
+	 * Whether this {@literal Buffer} is fixed-length or not.
+	 *
+	 * @return {@literal true} if this {@literal Buffer} is not fixed-length, {@literal false} otherwise.
+	 */
+	public boolean isDynamic() {
+		return dynamic;
+	}
+
+	/**
+	 * Provides the current position in the internal {@link ByteBuffer}.
+	 *
+	 * @return The current position.
+	 */
+	public int position() {
+		return (null == buffer ? 0 : buffer.position());
+	}
+
+	/**
+	 * Sets this buffer's position.
+	 *
+	 * @param pos
+	 * 		the new position
+	 *
+	 * @return this buffer
+	 */
+	public Buffer position(int pos) {
+		if(null != buffer) {
+			buffer.position(pos);
+		}
+		return this;
+	}
+
+	/**
+	 * Sets this buffer's limit.
+	 *
+	 * @param limit
+	 * 		the new limit
+	 *
+	 * @return this buffer
+	 */
+	public Buffer limit(int limit) {
+		if(null != buffer) {
+			buffer.limit(limit);
+		}
+		return this;
+	}
+
+	/**
+	 * Skips {@code len} bytes.
+	 *
+	 * @param len
+	 * 		the number of bytes to skip
+	 *
+	 * @return this buffer
+	 *
+	 * @throws BufferUnderflowException
+	 * 		if the skip exceeds the available bytes
+	 * @throws IllegalArgumentException
+	 * 		if len is negative
+	 */
+	public Buffer skip(int len) {
+		if(len < 0) {
+			throw new IllegalArgumentException("len must >= 0");
+		}
+		if(null != buffer) {
+			int pos = buffer.position();
+			buffer.position(pos + len);
+		}
+		return this;
+	}
+
+	/**
+	 * Provides the current limit of the internal {@link ByteBuffer}.
+	 *
+	 * @return The current limit.
+	 */
+	public int limit() {
+		return (null == buffer ? 0 : buffer.limit());
+	}
+
+	/**
+	 * Provides the current capacity of the internal {@link ByteBuffer}.
+	 *
+	 * @return The current capacity.
+	 */
+	public int capacity() {
+		return (null == buffer ? SMALL_BUFFER_SIZE : buffer.capacity());
+	}
+
+	/**
+	 * How many bytes available in this {@literal Buffer}. If reading, it is the number of bytes available to read. If
+	 * writing, it is the number of bytes available for writing.
+	 *
+	 * @return The number of bytes available in this {@literal Buffer}.
+	 */
+	public int remaining() {
+		return (null == buffer ? SMALL_BUFFER_SIZE : buffer.remaining());
+	}
+
+	/**
+	 * Clear the internal {@link ByteBuffer} by setting the {@link ByteBuffer#position(int)} to 0 and the limit to the
+	 * current capacity.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer clear() {
+		if(null != buffer) {
+			buffer.position(0);
+			buffer.limit(buffer.capacity());
+		}
+		return this;
+	}
+
+	/**
+	 * Compact the underlying {@link ByteBuffer}.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer compact() {
+		if(null != buffer) {
+			buffer.compact();
+		}
+		return this;
+	}
+
+	/**
+	 * Flip this {@literal Buffer}. Used after a write to prepare this {@literal Buffer} for reading.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer flip() {
+		if(null != buffer) {
+			buffer.flip();
+		}
+		return this;
+	}
+
+	/**
+	 * Rewind this {@literal Buffer} to the beginning. Prepares the {@literal Buffer} for reuse.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer rewind() {
+		if(null != buffer) {
+			buffer.rewind();
+		}
+		return this;
+	}
+
+	/**
+	 * Rewinds this buffer by {@code len} bytes.
+	 *
+	 * @param len
+	 * 		The number of bytes the rewind by
+	 *
+	 * @return this buffer
+	 *
+	 * @throws BufferUnderflowException
+	 * 		if the rewind would move past the start of the buffer
+	 * @throws IllegalArgumentException
+	 * 		if len is negative
+	 */
+	public Buffer rewind(int len) {
+		if(len < 0) {
+			throw new IllegalArgumentException("len must >= 0");
+		}
+		if(null != buffer) {
+			int pos = buffer.position();
+			buffer.position(pos - len);
+		}
+		return this;
+	}
+
+	/**
+	 * Create a new {@code Buffer} by calling {@link java.nio.ByteBuffer#duplicate()} on the underlying {@code
+	 * ByteBuffer}.
+	 *
+	 * @return the new {@code Buffer}
+	 */
+	public Buffer duplicate() {
+		return new Buffer(buffer.duplicate());
+	}
+
+	/**
+	 * Create a new {@code Buffer} by copying the underlying {@link ByteBuffer} into a newly-allocated {@code Buffer}.
+	 *
+	 * @return the new {@code Buffer}
+	 */
+	public Buffer copy() {
+		snapshot();
+		Buffer b = new Buffer(buffer.remaining(), false);
+		b.append(buffer);
+		reset();
+
+		return b.flip();
+	}
+
+	/**
+	 * Prepend the given {@link Buffer} to this {@literal Buffer}.
+	 *
+	 * @param b
+	 * 		The {@link Buffer} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(Buffer b) {
+		if(null == b) {
+			return this;
+		}
+		return prepend(b.buffer);
+	}
+
+	/**
+	 * Prepend the given {@link String } to this {@literal Buffer}.
+	 *
+	 * @param s
+	 * 		The {@link String} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(String s) {
+		if(null == s) {
+			return this;
+		}
+		return prepend(s.getBytes());
+	}
+
+	/**
+	 * Prepend the given {@code byte[]} array to this {@literal Buffer}.
+	 *
+	 * @param bytes
+	 * 		The {@code byte[]} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(byte[] bytes) {
+		shift(bytes.length);
+		buffer.put(bytes);
+		reset();
+		return this;
+	}
+
+	/**
+	 * Prepend the given {@link ByteBuffer} to this {@literal Buffer}.
+	 *
+	 * @param b
+	 * 		The {@link ByteBuffer} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(ByteBuffer b) {
+		if(null == b) {
+			return this;
+		}
+		shift(b.remaining());
+		this.buffer.put(b);
+		reset();
+		return this;
+
+	}
+
+	/**
+	 * Prepend the given {@code byte} to this {@literal Buffer}.
+	 *
+	 * @param b
+	 * 		The {@code byte} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(byte b) {
+		shift(1);
+		this.buffer.put(b);
+		reset();
+		return this;
+	}
+
+	/**
+	 * Prepend the given {@code char} to this existing {@literal Buffer}.
+	 *
+	 * @param c
+	 * 		The {@code char} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(char c) {
+		shift(2);
+		this.buffer.putChar(c);
+		reset();
+		return this;
+	}
+
+	/**
+	 * Prepend the given {@code short} to this {@literal Buffer}.
+	 *
+	 * @param s
+	 * 		The {@code short} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(short s) {
+		shift(2);
+		this.buffer.putShort(s);
+		reset();
+		return this;
+	}
+
+	/**
+	 * Prepend the given {@code int} to this {@literal Buffer}.
+	 *
+	 * @param i
+	 * 		The {@code int} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(int i) {
+		shift(4);
+		this.buffer.putInt(i);
+		reset();
+		return this;
+	}
+
+	/**
+	 * Prepend the given {@code long} to this {@literal Buffer}.
+	 *
+	 * @param l
+	 * 		The {@code long} to prepend.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer prepend(long l) {
+		shift(8);
+		this.buffer.putLong(l);
+		reset();
+		return this;
+	}
+
+	/**
+	 * Append the given String to this {@literal Buffer}.
+	 *
+	 * @param s
+	 * 		The String to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(String s) {
+		ensureCapacity(s.length());
+		buffer.put(s.getBytes());
+		return this;
+	}
+
+	/**
+	 * Append the given {@code short} to this {@literal Buffer}.
+	 *
+	 * @param s
+	 * 		The {@code short} to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(short s) {
+		ensureCapacity(2);
+		buffer.putShort(s);
+		return this;
+	}
+
+	/**
+	 * Append the given {@code int} to this {@literal Buffer}.
+	 *
+	 * @param i
+	 * 		The {@code int} to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(int i) {
+		ensureCapacity(4);
+		buffer.putInt(i);
+		return this;
+	}
+
+	/**
+	 * Append the given {@code long} to this {@literal Buffer}.
+	 *
+	 * @param l
+	 * 		The {@code long} to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(long l) {
+		ensureCapacity(8);
+		buffer.putLong(l);
+		return this;
+	}
+
+	/**
+	 * Append the given {@code char} to this {@literal Buffer}.
+	 *
+	 * @param c
+	 * 		The {@code char} to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(char c) {
+		ensureCapacity(2);
+		buffer.putChar(c);
+		return this;
+	}
+
+	/**
+	 * Append the given {@link ByteBuffer} to this {@literal Buffer}.
+	 *
+	 * @param buffers
+	 * 		The {@link ByteBuffer ByteBuffers} to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(ByteBuffer... buffers) {
+		for(ByteBuffer bb : buffers) {
+			ensureCapacity(bb.remaining());
+			buffer.put(bb);
+		}
+		return this;
+	}
+
+	/**
+	 * Append the given {@link Buffer} to this {@literal Buffer}.
+	 *
+	 * @param buffers
+	 * 		The {@link Buffer Buffers} to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(Buffer... buffers) {
+		for(Buffer b : buffers) {
+			int pos = (null == buffer ? 0 : buffer.position());
+			int len = b.remaining();
+			ensureCapacity(len);
+			buffer.put(b.byteBuffer());
+			buffer.position(pos + len);
+		}
+		return this;
+	}
+
+	/**
+	 * Append the given {@code byte} to this {@literal Buffer}.
+	 *
+	 * @param b
+	 * 		The {@code byte} to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(byte b) {
+		ensureCapacity(1);
+		buffer.put(b);
+		return this;
+	}
+
+	/**
+	 * Append the given {@code byte[]} to this {@literal Buffer}.
+	 *
+	 * @param b
+	 * 		The {@code byte[]} to append.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(byte[] b) {
+		ensureCapacity(b.length);
+		buffer.put(b);
+		return this;
+	}
+
+	/**
+	 * Append the given {@code byte[]} to this {@literal Buffer}, starting at the given index and continuing for the
+	 * given
+	 * length.
+	 *
+	 * @param b
+	 * 		the bytes to append
+	 * @param start
+	 * 		the index of where to start copying bytes
+	 * @param len
+	 * 		the len of the bytes to copy
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer append(byte[] b, int start, int len) {
+		ensureCapacity(b.length);
+		buffer.put(b, start, len);
+		return this;
+	}
+
+	/**
+	 * Get the first {@code byte} from this {@literal Buffer}.
+	 *
+	 * @return The first {@code byte}.
+	 */
+	public byte first() {
+		snapshot();
+		if(this.position > 0) {
+			buffer.position(0); // got to the 1st position
+		}
+		byte b = buffer.get(); // get the 1st byte
+		reset(); // go back to original pos
+		return b;
+	}
+
+	/**
+	 * Get the last {@code byte} from this {@literal Buffer}.
+	 *
+	 * @return The last {@code byte}.
+	 */
+	public byte last() {
+		int pos = buffer.position();
+		int limit = buffer.limit();
+		buffer.position(limit - 1); // go to right before last position
+		byte b = buffer.get(); // get the last byte
+		buffer.position(pos); // go back to original pos
+		return b;
+	}
+
+	/**
+	 * Read a single {@code byte} from the underlying {@link ByteBuffer}.
+	 *
+	 * @return The next {@code byte}.
+	 */
+	public byte read() {
+		if(null != buffer) {
+			return buffer.get();
+		}
+		throw new BufferUnderflowException();
+	}
+
+	/**
+	 * Read at least {@code b.length} bytes from the underlying {@link ByteBuffer}.
+	 *
+	 * @param b
+	 * 		The buffer to fill.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer read(byte[] b) {
+		if(null != buffer) {
+			buffer.get(b);
+		}
+		return this;
+	}
+
+	/**
+	 * Read the next {@code short} from the underlying {@link ByteBuffer}.
+	 *
+	 * @return The next {@code short}.
+	 */
+	public short readShort() {
+		if(null != buffer) {
+			return buffer.getShort();
+		}
+		throw new BufferUnderflowException();
+	}
+
+	/**
+	 * Read the next {@code int} from the underlying {@link ByteBuffer}.
+	 *
+	 * @return The next {@code int}.
+	 */
+	public int readInt() {
+		if(null != buffer) {
+			return buffer.getInt();
+		}
+		throw new BufferUnderflowException();
+	}
+
+	/**
+	 * Read the next {@code float} from the underlying {@link ByteBuffer}.
+	 *
+	 * @return The next {@code float}.
+	 */
+	public float readFloat() {
+		if(null != buffer) {
+			return buffer.getFloat();
+		}
+		throw new BufferUnderflowException();
+	}
+
+	/**
+	 * Read the next {@code double} from the underlying {@link ByteBuffer}.
+	 *
+	 * @return The next {@code double}.
+	 */
+	public double readDouble() {
+		if(null != buffer) {
+			return buffer.getDouble();
+		}
+		throw new BufferUnderflowException();
+	}
+
+	/**
+	 * Read the next {@code long} from the underlying {@link ByteBuffer}.
+	 *
+	 * @return The next {@code long}.
+	 */
+	public long readLong() {
+		if(null != buffer) {
+			return buffer.getLong();
+		}
+		throw new BufferUnderflowException();
+	}
+
+	/**
+	 * Read the next {@code char} from the underlying {@link ByteBuffer}.
+	 *
+	 * @return The next {@code char}.
+	 */
+	public char readChar() {
+		if(null != buffer) {
+			return buffer.getChar();
+		}
+		throw new BufferUnderflowException();
+	}
+
+	/**
+	 * Save the current buffer position and limit.
+	 */
+	public void snapshot() {
+		this.position = buffer.position();
+		this.limit = buffer.limit();
+	}
+
+	/**
+	 * Reset the buffer to the previously-saved position and limit.
+	 *
+	 * @return {@literal this}
+	 */
+	public Buffer reset() {
+		buffer.limit(limit);
+		buffer.position(position);
+		return this;
+	}
+
+	@Override
+	public Iterator<Byte> iterator() {
+		return new Iterator<Byte>() {
+			@Override
+			public boolean hasNext() {
+				return buffer.remaining() > 0;
+			}
+
+			@Override
+			public Byte next() {
+				return buffer.get();
+			}
+
+			@Override
+			public void remove() {
+				// NO-OP
+			}
+		};
+	}
+
+	@Override
+	public int read(ByteBuffer dst) throws IOException {
+		snapshot();
+		if(dst.remaining() < this.limit) {
+			buffer.limit(dst.remaining());
+		}
+		int pos = dst.position();
+		dst.put(buffer);
+		buffer.limit(this.limit);
+		return dst.position() - pos;
+	}
+
+	@Override
+	public int write(ByteBuffer src) throws IOException {
+		int pos = src.position();
+		append(src);
+		return src.position() - pos;
+	}
+
+	@Override
+	public boolean isOpen() {
+		return isDynamic();
+	}
+
+	@Override
+	public void close() throws IOException {
+		clear();
+	}
+
+	/**
+	 * Convert the contents of this buffer into a String using a UTF-8 {@link CharsetDecoder}.
+	 *
+	 * @return The contents of this {@literal Buffer} as a String.
+	 */
+	public String asString() {
+		if(null != buffer) {
+			return decode();
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * Slice a portion of this buffer and convert it to a String.
+	 *
+	 * @param start
+	 * 		start of the range.
+	 * @param end
+	 * 		end of the range.
+	 *
+	 * @return The contents of the given range as a String.
+	 */
+	public String substring(int start, int end) {
+		snapshot();
+
+		buffer.limit((end > start ? end : this.limit));
+		buffer.position(start);
+		String s = asString();
+
+		reset();
+		return s;
+	}
+
+	/**
+	 * Return the contents of this buffer copied into a {@code byte[]}.
+	 *
+	 * @return The contents of this buffer as a {@code byte[]}.
+	 */
+	public byte[] asBytes() {
+		if(null != buffer) {
+			snapshot();
+			byte[] b = new byte[buffer.remaining()];
+			buffer.get(b);
+			reset();
+			return b;
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * Create an {@link InputStream} capable of reading the bytes from the internal {@link ByteBuffer}.
+	 *
+	 * @return A new {@link InputStream}.
+	 */
+	public InputStream inputStream() {
+		return new BufferInputStream();
+	}
+
+	/**
+	 * Create a copy of the given range.
+	 *
+	 * @param start
+	 * 		start of the range.
+	 * @param len
+	 * 		end of the range.
+	 *
+	 * @return A new {@link Buffer}, constructed from the contents of the given range.
+	 */
+	public Buffer slice(int start, int len) {
+		snapshot();
+		ByteBuffer bb = ByteBuffer.allocate(len);
+		buffer.position(start);
+		bb.put(buffer);
+		reset();
+		bb.flip();
+		return new Buffer(bb);
+	}
+
+	/**
+	 * Split this buffer on the given delimiter.
+	 *
+	 * @param delimiter
+	 * 		The delimiter on which to split this buffer.
+	 *
+	 * @return An {@link Iterable} of {@link View Views} that point to the segments of this buffer.
+	 */
+	public Iterable<View> split(int delimiter) {
+		return split(new ArrayList<View>(), delimiter, false);
+	}
+
+	/**
+	 * Split this buffer on the given delimiter but save memory by reusing the given {@link List}.
+	 *
+	 * @param views
+	 * 		The list to store {@link View Views} in.
+	 * @param delimiter
+	 * 		The delimiter on which to split this buffer.
+	 *
+	 * @return An {@link Iterable} of {@link View Views} that point to the segments of this buffer.
+	 */
+	public Iterable<View> split(List<View> views, int delimiter) {
+		return split(views, delimiter, false);
+	}
+
+	/**
+	 * Split this buffer on the given delimiter and optionally leave the delimiter intact rather than stripping it.
+	 *
+	 * @param delimiter
+	 * 		The delimiter on which to split this buffer.
+	 * @param stripDelimiter
+	 * 		{@literal true} to ignore the delimiter, {@literal false} to leave it in the returned data.
+	 *
+	 * @return An {@link Iterable} of {@link View Views} that point to the segments of this buffer.
+	 */
+	public Iterable<View> split(int delimiter, boolean stripDelimiter) {
+		return split(new ArrayList<View>(), delimiter, stripDelimiter);
+	}
+
+	/**
+	 * Split this buffer on the given delimiter, save memory by reusing the given {@link List}, and optionally leave the
+	 * delimiter intact rather than stripping it.
+	 *
+	 * @param views
+	 * 		The list to store {@link View Views} in.
+	 * @param delimiter
+	 * 		The delimiter on which to split this buffer.
+	 * @param stripDelimiter
+	 * 		{@literal true} to ignore the delimiter, {@literal false} to leave it in the returned data.
+	 *
+	 * @return An {@link Iterable} of {@link View Views} that point to the segments of this buffer.
+	 */
+	public Iterable<View> split(List<View> views, int delimiter, boolean stripDelimiter) {
+		snapshot();
+
+		int start = this.position;
+		for(byte b : this) {
+			if(b == delimiter) {
+				int end = stripDelimiter ? buffer.position() - 1 : buffer.position();
+				views.add(createView(start, end));
+				start = end + (stripDelimiter ? 1 : 0);
+			}
+		}
+		if(start != buffer.position()) {
+			buffer.position(start);
+		}
+
+		reset();
+
+		return views;
+	}
+
+	/**
+	 * Split this buffer on the given delimiter and leave the delimiter on the end of each segment.
+	 *
+	 * @param delimiter
+	 * 		the multi-byte delimiter
+	 *
+	 * @return An {@link Iterable} of {@link View Views} that point to the segments of this buffer.
+	 */
+	public Iterable<View> split(Buffer delimiter) {
+		return split(new ArrayList<View>(), delimiter, false);
+	}
+
+	/**
+	 * Split this buffer on the given delimiter. The delimiter is stripped from the end of the segment if {@code
+	 * stripDelimiter} is {@code true}.
+	 *
+	 * @param delimiter
+	 * 		The multi-byte delimiter.
+	 * @param stripDelimiter
+	 * 		{@literal true} to ignore the delimiter, {@literal false} to leave it in the returned data.
+	 *
+	 * @return An {@link Iterable} of {@link View Views} that point to the segments of this buffer.
+	 */
+	public Iterable<View> split(Buffer delimiter, boolean stripDelimiter) {
+		return split(new ArrayList<View>(), delimiter, stripDelimiter);
+	}
+
+	/**
+	 * Split this buffer on the given delimiter. Save memory by reusing the provided {@code List}. The delimiter is
+	 * stripped from the end of the segment if {@code
+	 * stripDelimiter} is {@code true}.
+	 *
+	 * @param views
+	 * 		The already-allocated List to reuse.
+	 * @param delimiter
+	 * 		The multi-byte delimiter.
+	 * @param stripDelimiter
+	 * 		{@literal true} to ignore the delimiter, {@literal false} to leave it in the returned data.
+	 *
+	 * @return An {@link Iterable} of {@link View Views} that point to the segments of this buffer.
+	 */
+	public Iterable<View> split(List<View> views, Buffer delimiter, boolean stripDelimiter) {
+		snapshot();
+
+		byte[] delimBytes = delimiter.asBytes();
+		if(delimBytes.length == 0) {
+			return Collections.emptyList();
+		}
+
+		int start = this.position;
+		for(byte b : this) {
+			if(b != delimBytes[0]) {
+				continue;
+			}
+			int end = -1;
+			for(int i = 1; i < delimBytes.length; i++) {
+				if(read() == delimBytes[i]) {
+					end = stripDelimiter ? buffer.position() - delimBytes.length : buffer.position();
+				} else {
+					end = -1;
+					break;
+				}
+			}
+			if(end > 0) {
+				views.add(createView(start, end));
+				start = end + (stripDelimiter ? delimBytes.length : 0);
+			}
+		}
+		if(start != buffer.position()) {
+			buffer.position(start);
+		}
+
+		reset();
+
+		return views;
+	}
+
+	/**
+	 * Search the buffer and find the position of the first occurrence of the given {@code byte}.
+	 *
+	 * @param b
+	 * 		the {@code byte} to search for
+	 *
+	 * @return the position of the char in the buffer or {@code -1} if not found
+	 */
+	public int indexOf(byte b) {
+		return indexOf(b, buffer.position(), buffer.remaining());
+	}
+
+	/**
+	 * Search the buffer and find the position of the first occurrence of the given {@code byte} staring at the start
+	 * position and searching until (and including) the end position.
+	 *
+	 * @param b
+	 * 		the {@code byte} to search for
+	 * @param start
+	 * 		the position to start searching
+	 * @param end
+	 * 		the position at which to stop searching
+	 *
+	 * @return the position of the char in the buffer or {@code -1} if not found
+	 */
+	public int indexOf(byte b, int start, int end) {
+		snapshot();
+		if(buffer.position() != start) {
+			buffer.position(start);
+		}
+		int pos = -1;
+		while(buffer.hasRemaining() && buffer.position() < end) {
+			if(buffer.get() == b) {
+				pos = buffer.position();
+				break;
+			}
+		}
+		reset();
+		return pos;
+	}
+
+	/**
+	 * Create a {@link View} of the current range of this {@link Buffer}.
+	 *
+	 * @return The view of the buffer
+	 *
+	 * @see #position()
+	 * @see #limit()
+	 */
+	public View createView() {
+		snapshot();
+		return new View(position, limit);
+	}
+
+	/**
+	 * Create a {@link View} of the given range of this {@literal Buffer}.
+	 *
+	 * @param start
+	 * 		start of the range.
+	 * @param end
+	 * 		end of the range.
+	 *
+	 * @return A new {@link View} object that represents the given range.
+	 */
+	public View createView(int start, int end) {
+		snapshot();
+		return new View(start, end);
+	}
+
+	/**
+	 * Slice this buffer at the given positions. Useful for extracting multiple segments of data from a buffer when the
+	 * exact indices of that data is already known.
+	 *
+	 * @param positions
+	 * 		The start and end positions of the slices.
+	 *
+	 * @return A list of {@link View Views} pointing to the slices.
+	 */
+	public List<View> slice(int... positions) {
+		Assert.notNull(positions, "Positions cannot be null.");
+		if(positions.length == 0) {
+			return Collections.emptyList();
+		}
+
+		snapshot();
+
+		List<View> views = new ArrayList<View>();
+		int len = positions.length;
+		for(int i = 0; i < len; i++) {
+			int start = positions[i];
+			int end = (i + 1 < len ? positions[++i] : this.limit);
+			views.add(createView(start, end));
+			reset();
+		}
+
+		return views;
+	}
+
+	/**
+	 * Return the underlying {@link ByteBuffer}.
+	 *
+	 * @return The {@link ByteBuffer} in use.
+	 */
+	public ByteBuffer byteBuffer() {
+		return buffer;
+	}
+
+	@Override
+	public String toString() {
+		return (null != buffer ? buffer.toString() : "<EMPTY>");
+	}
+
+	@Override
+	public int compareTo(Buffer buffer) {
+		return (null != buffer ? this.buffer.compareTo(buffer.buffer) : -1);
+	}
+
+	private synchronized void ensureCapacity(int atLeast) {
+		if(null == buffer) {
+			buffer = ByteBuffer.allocate(SMALL_BUFFER_SIZE);
+			return;
+		}
+		int pos = buffer.position();
+		int cap = buffer.capacity();
+		if(dynamic && buffer.remaining() < atLeast) {
+			if(buffer.limit() < cap) {
+				// there's remaining capacity that hasn't been used yet
+				if(pos + atLeast > cap) {
+					expand();
+					cap = buffer.capacity();
+				}
+				buffer.limit(Math.min(pos + atLeast, cap));
+			} else {
+				expand();
+			}
+		} else if(pos + SMALL_BUFFER_SIZE > MAX_BUFFER_SIZE) {
+			throw new BufferOverflowException();
+		}
+	}
+
+	private void expand() {
+		snapshot();
+		ByteBuffer newBuff = (buffer.isDirect()
+		                      ? ByteBuffer.allocateDirect(buffer.limit() + SMALL_BUFFER_SIZE)
+		                      : ByteBuffer.allocate(buffer.limit() + SMALL_BUFFER_SIZE));
+		buffer.flip();
+		newBuff.put(buffer);
+		buffer = newBuff;
+		reset();
+	}
+
+	private String decode() {
+		if(null == decoder) {
+			decoder = UTF8.newDecoder();
+		}
+		snapshot();
+		try {
+			if(null == chars || chars.remaining() < buffer.remaining()) {
+				chars = CharBuffer.allocate(buffer.remaining());
+			} else {
+				chars.rewind();
+			}
+			decoder.reset();
+			CoderResult cr = decoder.decode(buffer, chars, true);
+			if(cr.isUnderflow()) {
+				decoder.flush(chars);
+			}
+			chars.flip();
+
+			return chars.toString();
+		} finally {
+			reset();
+		}
+	}
+
+	private void shift(int right) {
+		ByteBuffer currentBuffer;
+		if(null == buffer) {
+			ensureCapacity(right);
+			currentBuffer = buffer;
+		} else {
+			currentBuffer = buffer.slice();
+		}
+
+		int len = buffer.remaining();
+		int pos = buffer.position();
+		ensureCapacity(right + len);
+
+		buffer.position(pos + right);
+		buffer.put(currentBuffer);
+		buffer.position(pos);
+
+		snapshot();
+	}
+
+	private class BufferInputStream extends InputStream {
+		ByteBuffer buffer = Buffer.this.buffer.slice();
+
+		@Override
+		public int read(byte[] b) throws IOException {
+			int pos = buffer.position();
+			buffer.get(b);
+			syncPos();
+			return buffer.position() - pos;
+		}
+
+		@Override
+		public int read(byte[] b, int off, int len) throws IOException {
+			if(null == buffer || buffer.remaining() == 0) {
+				return -1;
+			}
+			byte[] bytes = asBytes();
+			int bytesLen = bytes.length;
+			System.arraycopy(bytes, 0, b, off, bytesLen);
+			if(len < bytesLen) {
+				buffer.position(position + len);
+			}
+			syncPos();
+			return bytesLen;
+		}
+
+		@Override
+		public long skip(long n) throws IOException {
+			if(n < buffer.remaining()) {
+				throw new IOException(new BufferUnderflowException());
+			}
+			int pos = buffer.position();
+			buffer.position((int)(pos + n));
+			syncPos();
+			return buffer.position() - pos;
+		}
+
+		@Override
+		public int available() throws IOException {
+			return buffer.remaining();
+		}
+
+		@Override
+		public void close() throws IOException {
+			buffer.position(buffer.limit());
+			syncPos();
+		}
+
+		@Override
+		public synchronized void mark(int readlimit) {
+			buffer.mark();
+			int pos = buffer.position();
+			int max = buffer.capacity() - pos;
+			int newLimit = Math.min(max, pos + readlimit);
+			buffer.limit(newLimit);
+		}
+
+		@Override
+		public synchronized void reset() throws IOException {
+			buffer.reset();
+			syncPos();
+		}
+
+		@Override
+		public boolean markSupported() {
+			return true;
+		}
+
+		@Override
+		public int read() throws IOException {
+			int b = buffer.get();
+			syncPos();
+			return b;
+		}
+
+		private void syncPos() {
+			int oldPos = Buffer.this.buffer.position();
+			Buffer.this.buffer.position(buffer.position() + oldPos);
+		}
+	}
+
+	/**
+	 * A {@literal View} represents a segment of a buffer. When {@link #get()} is called, the {@literal Buffer} is set to
+	 * the correct start and end points as given at creation time. After the view has been used, it is the responsibility
+	 * of the caller to {@link #reset()} the buffer if more manipulation is required. Otherwise, multiple views can be
+	 * created from a single buffer and used consecutively to extract portions of a buffer without expensive substrings.
+	 */
+	public class View implements Supplier<Buffer> {
+		private final int start;
+		private final int end;
+
+		private View(int start, int end) {
+			this.start = start;
+			this.end = end;
+		}
+
+		/**
+		 * Get the start of this range.
+		 *
+		 * @return start of the range.
+		 */
+		public int getStart() {
+			return start;
+		}
+
+		/**
+		 * Get the end of this range.
+		 *
+		 * @return end of the range.
+		 */
+		public int getEnd() {
+			return end;
+		}
+
+		@Override
+		public Buffer get() {
+			buffer.limit(end);
+			buffer.position(start);
+			return Buffer.this;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/BufferAllocator.java b/reactor-core/src/main/java/reactor/io/BufferAllocator.java
new file mode 100644
index 0000000..cb52139
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/BufferAllocator.java
@@ -0,0 +1,66 @@
+package reactor.io;
+
+import reactor.alloc.Allocator;
+import reactor.alloc.Reference;
+import reactor.alloc.ReferenceCountingAllocator;
+import reactor.function.Supplier;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * An {@link reactor.alloc.Allocator} implementation that allocates {@link reactor.io.Buffer Buffers}.
+ *
+ * @author Jon Brisbin
+ */
+public class BufferAllocator implements Allocator<Buffer> {
+
+	private final Allocator<Buffer> delegate;
+
+	/**
+	 * Create a {@code BufferAllocator} of size=256, direct=false, and bufferSize=Buffer.SMALL_BUFFER_SIZE.
+	 */
+	public BufferAllocator() {
+		this(256, false, Buffer.SMALL_BUFFER_SIZE);
+	}
+
+	/**
+	 * Create a {@code BufferAllocator}.
+	 *
+	 * @param poolSize
+	 * 		The number of Buffers to keep on hand.
+	 * @param direct
+	 * 		Whether or not to use direct buffers.
+	 * @param bufferSize
+	 * 		The size of the buffers.
+	 */
+	public BufferAllocator(int poolSize, final boolean direct, final int bufferSize) {
+		this.delegate = new ReferenceCountingAllocator<Buffer>(
+				poolSize,
+				new Supplier<Buffer>() {
+					@Override
+					public Buffer get() {
+						return new Buffer(direct
+						                  ? ByteBuffer.allocateDirect(bufferSize)
+						                  : ByteBuffer.allocate(bufferSize));
+					}
+				}
+		);
+	}
+
+	@Override
+	public Reference<Buffer> allocate() {
+		return delegate.allocate();
+	}
+
+	@Override
+	public List<Reference<Buffer>> allocateBatch(int size) {
+		return delegate.allocateBatch(size);
+	}
+
+	@Override
+	public void release(List<Reference<Buffer>> batch) {
+		delegate.release(batch);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/ByteArrayCodec.java b/reactor-core/src/main/java/reactor/io/encoding/ByteArrayCodec.java
new file mode 100644
index 0000000..bc1b8ac
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/ByteArrayCodec.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io.encoding;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+
+/**
+ * A simple {@link reactor.io.encoding.Codec} implementation that turns a {@link Buffer} into a {@code byte[]} and
+ * visa-versa.
+ *
+ * @author Jon Brisbin
+ */
+public class ByteArrayCodec implements Codec<Buffer, byte[], byte[]> {
+
+	@Override
+	public Function<Buffer, byte[]> decoder(final Consumer<byte[]> next) {
+		return new Function<Buffer, byte[]>() {
+			@Override
+			public byte[] apply(Buffer buffer) {
+				byte[] bytes = buffer.asBytes();
+				buffer.skip(bytes.length);
+				if(null != next) {
+					next.accept(bytes);
+					return null;
+				} else {
+					return bytes;
+				}
+			}
+		};
+	}
+
+	@Override
+	public Function<byte[], Buffer> encoder() {
+		return new Function<byte[], Buffer>() {
+			@Override
+			public Buffer apply(byte[] bytes) {
+				return Buffer.wrap(bytes);
+			}
+		};
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/Codec.java b/reactor-core/src/main/java/reactor/io/encoding/Codec.java
new file mode 100644
index 0000000..99e0666
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/Codec.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io.encoding;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+
+/**
+ * Implementations of a {@literal Codec} are responsible for decoding a {@code SRC} into an
+ * instance of {@code IN} and passing that to the given {@link reactor.function.Consumer}. A
+ * codec also provides an encoder to take an instance of {@code OUT} and encode to an
+ * instance of {@code SRC}.
+ *
+ * @param <SRC> The type that the codec decodes from and encodes to
+ * @param <IN> The type produced by decoding
+ * @param <OUT> The type consumed by encoding
+ *
+ * @author Jon Brisbin
+ */
+public interface Codec<SRC, IN, OUT> {
+
+	/**
+	 * Provide the caller with a decoder to turn a source object into an instance of the input
+	 * type.
+	 *
+	 * @param next The {@link Consumer} to call after the object has been decoded.
+	 * @return The decoded object.
+	 */
+	Function<SRC, IN> decoder(Consumer<IN> next);
+
+	/**
+	 * Provide the caller with an encoder to turn an output object into an instance of the source
+	 * type.
+	 *
+	 * @return The encoded source object.
+	 */
+	Function<OUT, SRC> encoder();
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/DelimitedCodec.java b/reactor-core/src/main/java/reactor/io/encoding/DelimitedCodec.java
new file mode 100644
index 0000000..b9d962d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/DelimitedCodec.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io.encoding;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.io.Buffer.View;
+import reactor.io.encoding.Codec;
+
+/**
+ * An implementation of {@link reactor.io.encoding.Codec} that decodes by splitting a {@link Buffer} into segments
+ * based on a delimiter and encodes by appending its delimiter to each piece of output.
+ * During decoding the delegate is used to process each segment. During encoding the delegate
+ * is used to create a buffer for each piece of output to which the delimiter is then appended.
+ *
+ * @param <IN> The type that will be produced by decoding
+ * @param <OUT> The type that will be consumed by encoding
+ *
+ * @author Jon Brisbin
+ */
+public class DelimitedCodec<IN, OUT> implements Codec<Buffer, IN, OUT> {
+
+	private final Codec<Buffer, IN, OUT> delegate;
+	private final byte                   delimiter;
+	private final boolean                stripDelimiter;
+
+	/**
+	 * Create a line-feed-delimited codec, using the given {@code Codec} as a delegate.
+	 *
+	 * @param delegate The delegate {@link Codec}.
+	 */
+	public DelimitedCodec(Codec<Buffer, IN, OUT> delegate) {
+		this((byte) 10, true, delegate);
+
+	}
+
+	/**
+	 * Create a line-feed-delimited codec, using the given {@code Codec} as a delegate.
+	 *
+	 * @param stripDelimiter Flag to indicate whether the delimiter should be stripped from the
+	 *                       chunk or not during decoding.
+	 * @param delegate       The delegate {@link Codec}.
+	 */
+	public DelimitedCodec(boolean stripDelimiter, Codec<Buffer, IN, OUT> delegate) {
+		this((byte) 10, stripDelimiter, delegate);
+
+	}
+
+	/**
+	 * Create a delimited codec using the given delimiter and using the given {@code Codec}
+	 * as a delegate.
+	 *
+	 * @param delimiter      The delimiter to use.
+	 * @param stripDelimiter Flag to indicate whether the delimiter should be stripped from the
+	 *                       chunk or not during decoding.
+	 * @param delegate       The delegate {@link Codec}.
+	 */
+	public DelimitedCodec(byte delimiter, boolean stripDelimiter, Codec<Buffer, IN, OUT> delegate) {
+		this.delimiter = delimiter;
+		this.stripDelimiter = stripDelimiter;
+		this.delegate = delegate;
+	}
+
+	@Override
+	public Function<Buffer, IN> decoder(Consumer<IN> next) {
+		return new DelimitedDecoder(next);
+	}
+
+	@Override
+	public Function<OUT, Buffer> encoder() {
+		return new DelimitedEncoder();
+	}
+
+	private class DelimitedDecoder implements Function<Buffer, IN> {
+		private final Function<Buffer, IN> decoder;
+
+		DelimitedDecoder(Consumer<IN> next) {
+			this.decoder = delegate.decoder(next);
+		}
+
+		@Override
+		public IN apply(Buffer bytes) {
+			if (bytes.remaining() == 0) {
+				return null;
+			}
+
+			Iterable<View> views = bytes.split(delimiter, stripDelimiter);
+
+			int limit = bytes.limit();
+			int position = bytes.position();
+
+			for (Buffer.View view : views) {
+				Buffer b = view.get();
+				decoder.apply(b);
+			}
+
+			bytes.limit(limit);
+			bytes.position(position);
+
+			return null;
+		}
+	}
+
+	private class DelimitedEncoder implements Function<OUT, Buffer> {
+		Function<OUT, Buffer> encoder = delegate.encoder();
+
+		@Override
+		@SuppressWarnings("resource")
+		public Buffer apply(OUT out) {
+			Buffer buffer = new Buffer();
+			Buffer encoded = encoder.apply(out);
+			if (null != encoded && encoded.remaining() > 0) {
+				buffer.append(encoded).append(delimiter);
+			}
+			return buffer.flip();
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/Frame.java b/reactor-core/src/main/java/reactor/io/encoding/Frame.java
new file mode 100644
index 0000000..ca2f36d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/Frame.java
@@ -0,0 +1,26 @@
+package reactor.io.encoding;
+
+import reactor.io.Buffer;
+
+/**
+ * @author Jon Brisbin
+ */
+public class Frame {
+
+	private final Buffer prefix;
+	private final Buffer data;
+
+	public Frame(Buffer prefix, Buffer data) {
+		this.prefix = prefix;
+		this.data = data;
+	}
+
+	public Buffer getPrefix() {
+		return prefix;
+	}
+
+	public Buffer getData() {
+		return data;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/FrameCodec.java b/reactor-core/src/main/java/reactor/io/encoding/FrameCodec.java
new file mode 100644
index 0000000..7f0dd5a
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/FrameCodec.java
@@ -0,0 +1,155 @@
+package reactor.io.encoding;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+
+/**
+ * {@code Codec} for decoding data into length-field-based {@link reactor.io.encoding.Frame Frames}.
+ *
+ * @author Jon Brisbin
+ */
+public class FrameCodec implements Codec<Buffer, Frame, Frame> {
+
+	public enum LengthField {
+		SHORT, INT, LONG
+	}
+
+	private final FrameEncoder encoder = new FrameEncoder();
+
+	private final LengthField lengthField;
+	private final int         prefixLength;
+	private final int         minRequiredLen;
+
+	public FrameCodec(int prefixLength, LengthField lengthField) {
+		this.prefixLength = prefixLength;
+		this.lengthField = lengthField;
+		this.minRequiredLen = lengthFieldLength(lengthField) + prefixLength;
+	}
+
+	@Override
+	public Function<Buffer, Frame> decoder(Consumer<Frame> next) {
+		return new FrameDecoder(next);
+	}
+
+	@Override
+	public Function<Frame, Buffer> encoder() {
+		return encoder;
+	}
+
+	private class FrameDecoder implements Function<Buffer, Frame> {
+		private final Consumer<Frame> next;
+
+		private FrameDecoder(Consumer<Frame> next) {
+			this.next = next;
+		}
+
+		@Override
+		public Frame apply(Buffer buffer) {
+			while(buffer.remaining() > minRequiredLen) {
+				int pos = buffer.position();
+				int limit = buffer.limit();
+
+				Buffer.View prefix = readPrefix(buffer);
+				if(null == prefix) {
+					// insufficient data
+					buffer.limit(limit);
+					buffer.position(pos);
+					return null;
+				}
+
+				Buffer.View data = readData(buffer);
+				if(null == data) {
+					// insufficient data
+					buffer.limit(limit);
+					buffer.position(pos);
+					return null;
+				}
+
+				Buffer prefixBuff = new Buffer(prefixLength, true).append(prefix.get()).flip();
+				Buffer dataBuff = new Buffer(data.getEnd() - data.getStart(), true).append(data.get()).flip();
+
+				buffer.limit(limit);
+
+				Frame f = new Frame(prefixBuff, dataBuff);
+				if(null != next) {
+					next.accept(f);
+				} else {
+					return f;
+				}
+			}
+			return null;
+		}
+
+		private Buffer.View readPrefix(Buffer buffer) {
+			if(buffer.remaining() < prefixLength) {
+				return null;
+			}
+
+			int pos = buffer.position();
+			Buffer.View prefix = buffer.createView(pos, pos + prefixLength);
+			buffer.position(pos + prefixLength);
+
+			return prefix;
+		}
+
+		private int readLen(Buffer buffer) {
+			switch(lengthField) {
+				case SHORT:
+					if(buffer.remaining() > 2) {
+						return buffer.readShort();
+					}
+					break;
+				case INT:
+					if(buffer.remaining() > 4) {
+						return buffer.readInt();
+					}
+					break;
+				case LONG:
+					if(buffer.remaining() > 8) {
+						return (int)buffer.readLong();
+					}
+					break;
+			}
+
+			return -1;
+		}
+
+		private Buffer.View readData(Buffer buffer) {
+			int pos = buffer.position();
+			int limit = buffer.limit();
+
+			int len = readLen(buffer);
+			if(len == -1 || buffer.remaining() < len) {
+				buffer.limit(limit);
+				buffer.position(pos);
+				return null;
+			}
+
+			pos = buffer.position();
+			Buffer.View data = buffer.createView(pos, pos + len);
+			buffer.position(pos + len);
+
+			return data;
+		}
+	}
+
+	private class FrameEncoder implements Function<Frame, Buffer> {
+		@Override
+		public Buffer apply(Frame frame) {
+			return null;
+		}
+	}
+
+	private static int lengthFieldLength(LengthField lf) {
+		switch(lf) {
+			case SHORT:
+				return 2;
+			case INT:
+				return 4;
+			default:
+				return 8;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/JavaSerializationCodec.java b/reactor-core/src/main/java/reactor/io/encoding/JavaSerializationCodec.java
new file mode 100644
index 0000000..ae03eef
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/JavaSerializationCodec.java
@@ -0,0 +1,72 @@
+package reactor.io.encoding;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+
+import java.io.*;
+
+/**
+ * {@code Codec} to transform Java objects into {@link reactor.io.Buffer Buffers} and visa-versa.
+ *
+ * @author Jon Brisbin
+ */
+public class JavaSerializationCodec<T> implements Codec<Buffer, T, T> {
+
+	private final Encoder encoder = new Encoder();
+
+	@Override
+	public Function<Buffer, T> decoder(Consumer<T> next) {
+		return new Decoder(next);
+	}
+
+	@Override
+	public Function<T, Buffer> encoder() {
+		return encoder;
+	}
+
+	private class Decoder implements Function<Buffer, T> {
+		private final Consumer<T> next;
+
+		private Decoder(Consumer<T> next) {
+			this.next = next;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public T apply(Buffer buff) {
+			if(buff.remaining() <= 0) {
+				return null;
+			}
+			try {
+				T obj = (T)new ObjectInputStream(new ByteArrayInputStream(buff.asBytes())).readObject();
+				if(null != next) {
+					next.accept(obj);
+					return null;
+				} else {
+					return obj;
+				}
+			} catch(Exception e) {
+				throw new IllegalStateException(e.getMessage(), e);
+			}
+		}
+	}
+
+	private class Encoder implements Function<T, Buffer> {
+		@Override
+		public Buffer apply(T t) {
+			ByteArrayOutputStream baos = new ByteArrayOutputStream();
+			try {
+				ObjectOutputStream oos = new ObjectOutputStream(baos);
+				oos.writeObject(t);
+				oos.flush();
+				oos.close();
+			} catch(IOException e) {
+				throw new IllegalStateException(e.getMessage(), e);
+			}
+
+			return Buffer.wrap(baos.toByteArray());
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/LengthFieldCodec.java b/reactor-core/src/main/java/reactor/io/encoding/LengthFieldCodec.java
new file mode 100644
index 0000000..6f20d61
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/LengthFieldCodec.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io.encoding;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.util.Assert;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A codec that uses a length-field at the start of each chunk to denote the chunk's size.
+ * During decoding the delegate is used to process each chunk. During encoding the delegate
+ * is used to encode each piece of output into a buffer. The buffer is then output, with its
+ * length prepended.
+ *
+ * @param <IN>
+ * 		The type that will be produced by decoding
+ * @param <OUT>
+ * 		The type that will be consumed by encoding
+ *
+ * @author Jon Brisbin
+ */
+public class LengthFieldCodec<IN, OUT> implements Codec<Buffer, IN, OUT> {
+
+	private final int                    lengthFieldLength;
+	private final Codec<Buffer, IN, OUT> delegate;
+
+	/**
+	 * Create a length-field codec that reads the first integer as the length of the
+	 * remaining message.
+	 *
+	 * @param delegate
+	 * 		The delegate {@link Codec}.
+	 */
+	public LengthFieldCodec(Codec<Buffer, IN, OUT> delegate) {
+		this(4, delegate);
+	}
+
+	/**
+	 * Create a length-field codec that reads the first short, integer, or long as the
+	 * length of the remaining message, and prepends a short, integer, long to its output.
+	 *
+	 * @param lengthFieldLength
+	 * 		The size of the length field. Valid values are 4 (int) or 8 (long).
+	 * @param delegate
+	 * 		The delegate {@link Codec}.
+	 */
+	public LengthFieldCodec(int lengthFieldLength, Codec<Buffer, IN, OUT> delegate) {
+		Assert.state(lengthFieldLength == 2 || lengthFieldLength == 4 || lengthFieldLength == 8,
+		             "lengthFieldLength should be 2 (short), 4 (int), or 8 (long).");
+		this.lengthFieldLength = lengthFieldLength;
+		this.delegate = delegate;
+	}
+
+	@Override
+	public Function<Buffer, IN> decoder(Consumer<IN> next) {
+		return new LengthFieldDecoder(next);
+	}
+
+	@Override
+	public Function<OUT, Buffer> encoder() {
+		return new LengthFieldEncoder();
+	}
+
+	private class LengthFieldDecoder implements Function<Buffer, IN> {
+		private final Function<Buffer, IN> decoder;
+
+		private LengthFieldDecoder(Consumer<IN> next) {
+			this.decoder = delegate.decoder(next);
+		}
+
+		@Override
+		public IN apply(Buffer buffer) {
+			while(buffer.remaining() > lengthFieldLength) {
+				int expectedLen = readLen(buffer);
+				if(expectedLen < 0 || expectedLen > buffer.remaining()) {
+					// This Buffer doesn't contain a full frame of data
+					// reset to the start and bail out
+					buffer.rewind(lengthFieldLength);
+					return null;
+				}
+				// We have at least a full frame of data
+
+				// save the position and limit so we can reset it later
+				int pos = buffer.position();
+				int limit = buffer.limit();
+
+				// create a view of the frame
+				Buffer.View v = buffer.createView(pos, pos + expectedLen);
+				// call the delegate decoder with the full frame
+				IN in = decoder.apply(v.get());
+				// reset the limit
+				buffer.byteBuffer().limit(limit);
+				if(buffer.position() == pos) {
+					// the pointer hasn't advanced, advance it
+					buffer.skip(expectedLen);
+				}
+				if(null != in) {
+					// no Consumer was invoked, return this data
+					return in;
+				}
+			}
+
+			return null;
+		}
+
+		private int readLen(Buffer buffer) {
+			if(lengthFieldLength == 4) {
+				return buffer.readInt();
+			} else if(lengthFieldLength == 2) {
+				return buffer.readShort();
+			} else {
+				return (int)buffer.readLong();
+			}
+		}
+	}
+
+	private class LengthFieldEncoder implements Function<OUT, Buffer> {
+		private final Function<OUT, Buffer> encoder = delegate.encoder();
+
+		@Override
+		public Buffer apply(OUT out) {
+			if(null == out) {
+				return null;
+			}
+
+			Buffer encoded = encoder.apply(out);
+			if(null != encoded && encoded.remaining() > 0) {
+				int len = encoded.remaining();
+				ByteBuffer bb = null;
+				if(lengthFieldLength == 4) {
+					bb = ByteBuffer.allocate(len + 4);
+					bb.putInt(len);
+				} else if(lengthFieldLength == 2) {
+					bb = ByteBuffer.allocate(len + 2);
+					bb.putShort((short)len);
+				} else if(lengthFieldLength == 8) {
+					bb = ByteBuffer.allocate(len + 8);
+					bb.putLong((long)len);
+				}
+				if(null != bb) {
+					bb.put(encoded.byteBuffer()).flip();
+					return new Buffer(bb);
+				}
+			}
+			return encoded;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/PassThroughCodec.java b/reactor-core/src/main/java/reactor/io/encoding/PassThroughCodec.java
new file mode 100644
index 0000000..3d2d596
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/PassThroughCodec.java
@@ -0,0 +1,64 @@
+package reactor.io.encoding;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+
+/**
+ * A simple {@code Codec} that uses the source object as both input and output. Override the {@link
+ * #beforeAccept(Object)} and {@link #beforeApply(Object)} methods to intercept data coming in and going out
+ * (respectively).
+ *
+ * @author Jon Brisbin
+ */
+public class PassThroughCodec<SRC> implements Codec<SRC, SRC, SRC> {
+	@Override
+	public Function<SRC, SRC> decoder(final Consumer<SRC> next) {
+		return new Function<SRC, SRC>() {
+			@Override
+			public SRC apply(SRC src) {
+				if(null != next) {
+					next.accept(beforeAccept(src));
+					return null;
+				} else {
+					return beforeAccept(src);
+				}
+			}
+		};
+	}
+
+	@Override
+	public Function<SRC, SRC> encoder() {
+		return new Function<SRC, SRC>() {
+			@Override
+			public SRC apply(SRC src) {
+				return beforeApply(src);
+			}
+		};
+	}
+
+	/**
+	 * Override to intercept the source object before it is passed into the next {@link reactor.function.Consumer} or
+	 * returned to the caller if a {@link reactor.function.Consumer} is not set.
+	 *
+	 * @param src The source object.
+	 *
+	 * @return
+	 */
+	protected SRC beforeAccept(SRC src) {
+		// NO-OP
+		return src;
+	}
+
+	/**
+	 * Override to intercept the source object before it is returned for output.
+	 *
+	 * @param src
+	 *
+	 * @return
+	 */
+	protected SRC beforeApply(SRC src) {
+		// NO-OP
+		return src;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/SerializationCodec.java b/reactor-core/src/main/java/reactor/io/encoding/SerializationCodec.java
new file mode 100644
index 0000000..54b0885
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/SerializationCodec.java
@@ -0,0 +1,143 @@
+package reactor.io.encoding;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.util.Assert;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Abstract base class for {@code Codec Codecs} that perform serialization of objects. Optionally handles writing class
+ * names so that an object that is serialized can be properly instantiated with full type information on the other end.
+ *
+ * @author Jon Brisbin
+ */
+public abstract class SerializationCodec<E, IN, OUT> implements Codec<Buffer, IN, OUT> {
+
+	private final Logger                 log   = LoggerFactory.getLogger(getClass());
+	private final Map<String, Class<IN>> types = new ConcurrentHashMap<String, Class<IN>>();
+	private final E       engine;
+	private final boolean lengthFieldFraming;
+
+	/**
+	 * Create a {@code SerializationCodec} using the given engine and specifying whether or not to prepend a length field
+	 * to frame the message.
+	 *
+	 * @param engine
+	 * 		the engine which will perform the serialization
+	 * @param lengthFieldFraming
+	 * 		{@code true} to prepend a length field, or {@code false} to skip
+	 */
+	protected SerializationCodec(E engine, boolean lengthFieldFraming) {
+		this.engine = engine;
+		this.lengthFieldFraming = lengthFieldFraming;
+	}
+
+	@Override
+	public Function<Buffer, IN> decoder(Consumer<IN> next) {
+		if(lengthFieldFraming) {
+			return new LengthFieldCodec<IN, OUT>(new DelegateCodec()).decoder(next);
+		} else {
+			return new DelegateCodec().decoder(next);
+		}
+	}
+
+	@Override
+	public Function<OUT, Buffer> encoder() {
+		if(lengthFieldFraming) {
+			return new LengthFieldCodec<IN, OUT>(new DelegateCodec()).encoder();
+		} else {
+			return new DelegateCodec().encoder();
+		}
+	}
+
+	protected E getEngine() {
+		return engine;
+	}
+
+	protected abstract Function<byte[], IN> deserializer(E engine, Class<IN> type, Consumer<IN> next);
+
+	protected abstract Function<OUT, byte[]> serializer(E engine);
+
+	private String readTypeName(Buffer buffer) {
+		int len = buffer.readInt();
+		Assert.isTrue(buffer.remaining() > len,
+		              "Incomplete buffer. Must contain " + len + " bytes, "
+				              + "but only " + buffer.remaining() + " were found.");
+		byte[] bytes = new byte[len];
+		buffer.read(bytes);
+		return new String(bytes);
+	}
+
+	private Buffer writeTypeName(Class<?> type, byte[] bytes) {
+		String typeName = type.getName();
+		int len = typeName.length();
+		Buffer buffer = new Buffer(4 + len + bytes.length, true);
+		return buffer.append(len)
+		             .append(typeName)
+		             .append(bytes)
+		             .flip();
+
+	}
+
+	public Class<IN> readType(Buffer buffer) {
+		String typeName = readTypeName(buffer);
+		return getType(typeName);
+	}
+
+	@SuppressWarnings("unchecked")
+	private Class<IN> getType(String name) {
+		Class<IN> type = types.get(name);
+		if(null == type) {
+			try {
+				type = (Class<IN>)Class.forName(name);
+			} catch(ClassNotFoundException e) {
+				throw new IllegalArgumentException(e.getMessage(), e);
+			}
+			types.put(name, type);
+		}
+		return type;
+	}
+
+	private class DelegateCodec implements Codec<Buffer, IN, OUT> {
+		@Override
+		public Function<Buffer, IN> decoder(final Consumer<IN> next) {
+			return new Function<Buffer, IN>() {
+				@Override
+				public IN apply(Buffer buffer) {
+					try {
+						return deserializer(engine, readType(buffer), next).apply(buffer.asBytes());
+					} catch(RuntimeException e) {
+						if(log.isErrorEnabled()) {
+							log.error("Could not decode " + buffer, e);
+						}
+						throw e;
+					}
+				}
+			};
+		}
+
+		@Override
+		public Function<OUT, Buffer> encoder() {
+			final Function<OUT, byte[]> fn = serializer(engine);
+			return new Function<OUT, Buffer>() {
+				@Override
+				public Buffer apply(OUT o) {
+					try {
+						return writeTypeName(o.getClass(), fn.apply(o));
+					} catch(RuntimeException e) {
+						if(log.isErrorEnabled()) {
+							log.error("Could not encode " + o, e);
+						}
+						throw e;
+					}
+				}
+			};
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/StandardCodecs.java b/reactor-core/src/main/java/reactor/io/encoding/StandardCodecs.java
new file mode 100644
index 0000000..7bc7a4c
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/StandardCodecs.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io.encoding;
+
+import reactor.io.Buffer;
+
+/**
+ * A selection of standard codecs.
+ *
+ * @author Jon Brisbin
+ */
+public abstract class StandardCodecs {
+
+	private StandardCodecs() {
+	}
+
+	public static final Codec<Buffer, Buffer, Buffer> PASS_THROUGH_CODEC = new PassThroughCodec<Buffer>();
+
+	/**
+	 * A {@link reactor.io.encoding.ByteArrayCodec}.
+	 */
+	public static final ByteArrayCodec BYTE_ARRAY_CODEC = new ByteArrayCodec();
+
+	/**
+	 * A {@link StringCodec}.
+	 */
+	public static final StringCodec STRING_CODEC = new StringCodec();
+
+	/**
+	 * A {@link DelimitedCodec} that works with {@code String} data delimited by a line-feed ({@code '\n'}) character
+	 */
+	public static final DelimitedCodec<String, String> LINE_FEED_CODEC = new DelimitedCodec<String,
+			String>(STRING_CODEC);
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/StringCodec.java b/reactor-core/src/main/java/reactor/io/encoding/StringCodec.java
new file mode 100644
index 0000000..bbd3176
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/StringCodec.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io.encoding;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+/**
+ * @author Jon Brisbin
+ */
+public class StringCodec implements Codec<Buffer, String, String> {
+
+	private final Charset utf8 = Charset.forName("UTF-8");
+
+	@Override
+	public Function<Buffer, String> decoder(Consumer<String> next) {
+		return new StringDecoder(next);
+	}
+
+	@Override
+	public Function<String, Buffer> encoder() {
+		return new StringEncoder();
+	}
+
+	private class StringDecoder implements Function<Buffer, String> {
+		private final Consumer<String> next;
+		private final CharsetDecoder decoder = utf8.newDecoder();
+
+		private StringDecoder(Consumer<String> next) {
+			this.next = next;
+		}
+
+		@Override
+		public String apply(Buffer bytes) {
+			try {
+				String s = decoder.decode(bytes.byteBuffer()).toString();
+				if (null != next) {
+					next.accept(s);
+					return null;
+				} else {
+					return s;
+				}
+			} catch (CharacterCodingException e) {
+				throw new IllegalStateException(e);
+			}
+		}
+	}
+
+	private class StringEncoder implements Function<String, Buffer> {
+		private final CharsetEncoder encoder = utf8.newEncoder();
+
+		@Override
+		public Buffer apply(String s) {
+			try {
+				ByteBuffer bb = encoder.encode(CharBuffer.wrap(s));
+				return new Buffer(bb);
+			} catch (CharacterCodingException e) {
+				throw new IllegalStateException(e);
+			}
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/compress/CompressionCodec.java b/reactor-core/src/main/java/reactor/io/encoding/compress/CompressionCodec.java
new file mode 100644
index 0000000..9bdece8
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/compress/CompressionCodec.java
@@ -0,0 +1,74 @@
+package reactor.io.encoding.compress;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+
+import java.io.*;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class CompressionCodec<IN, OUT> implements Codec<Buffer, IN, OUT> {
+
+	private final Codec<Buffer, IN, OUT> delegate;
+
+	protected CompressionCodec(Codec<Buffer, IN, OUT> delegate) {
+		this.delegate = delegate;
+	}
+
+	@Override
+	public Function<Buffer, IN> decoder(final Consumer<IN> next) {
+		return new Function<Buffer, IN>() {
+			@Override
+			public IN apply(Buffer buffer) {
+				try {
+					ByteArrayInputStream bin = new ByteArrayInputStream(buffer.asBytes());
+					InputStream zin = createInputStream(bin);
+					Buffer newBuff = new Buffer();
+					while(zin.available() > 0) {
+						newBuff.append((byte)zin.read());
+					}
+					zin.close();
+					IN in = delegate.decoder(null).apply(newBuff.flip());
+					if(null != next) {
+						next.accept(in);
+						return null;
+					} else {
+						return in;
+					}
+				} catch(IOException e) {
+					throw new IllegalStateException(e.getMessage(), e);
+				}
+			}
+		};
+	}
+
+	@Override
+	public Function<OUT, Buffer> encoder() {
+		return new Function<OUT, Buffer>() {
+			@Override
+			public Buffer apply(OUT out) {
+				Buffer buff = delegate.encoder().apply(out);
+				try {
+					ByteArrayOutputStream bout = new ByteArrayOutputStream();
+					OutputStream zout = createOutputStream(bout);
+					zout.write(buff.asBytes());
+					zout.flush();
+					bout.flush();
+					zout.close();
+					return Buffer.wrap(bout.toByteArray());
+				} catch(IOException e) {
+					throw new IllegalStateException(e.getMessage(), e);
+				}
+			}
+		};
+	}
+
+	protected abstract InputStream createInputStream(InputStream parent) throws IOException;
+
+	protected abstract OutputStream createOutputStream(OutputStream parent) throws IOException;
+
+}
+
diff --git a/reactor-core/src/main/java/reactor/io/encoding/compress/GzipCodec.java b/reactor-core/src/main/java/reactor/io/encoding/compress/GzipCodec.java
new file mode 100644
index 0000000..3b8bd12
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/compress/GzipCodec.java
@@ -0,0 +1,31 @@
+package reactor.io.encoding.compress;
+
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * @author Jon Brisbin
+ */
+public class GzipCodec<IN, OUT> extends CompressionCodec<IN, OUT> {
+
+	public GzipCodec(Codec<Buffer, IN, OUT> delegate) {
+		super(delegate);
+	}
+
+	@Override
+	protected InputStream createInputStream(InputStream parent) throws IOException {
+		return new GZIPInputStream(parent);
+	}
+
+	@Override
+	protected OutputStream createOutputStream(OutputStream parent) throws IOException {
+		return new GZIPOutputStream(parent);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/compress/SnappyCodec.java b/reactor-core/src/main/java/reactor/io/encoding/compress/SnappyCodec.java
new file mode 100644
index 0000000..676a420
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/compress/SnappyCodec.java
@@ -0,0 +1,31 @@
+package reactor.io.encoding.compress;
+
+import org.xerial.snappy.SnappyInputStream;
+import org.xerial.snappy.SnappyOutputStream;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * @author Jon Brisbin
+ */
+public class SnappyCodec<IN, OUT> extends CompressionCodec<IN, OUT> {
+
+	public SnappyCodec(Codec<Buffer, IN, OUT> delegate) {
+		super(delegate);
+	}
+
+	@Override
+	protected InputStream createInputStream(InputStream parent) throws IOException {
+		return new SnappyInputStream(parent);
+	}
+
+	@Override
+	protected OutputStream createOutputStream(OutputStream parent) throws IOException {
+		return new SnappyOutputStream(parent);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/json/JacksonJsonCodec.java b/reactor-core/src/main/java/reactor/io/encoding/json/JacksonJsonCodec.java
new file mode 100644
index 0000000..58e2717
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/json/JacksonJsonCodec.java
@@ -0,0 +1,60 @@
+package reactor.io.encoding.json;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.encoding.SerializationCodec;
+
+import java.io.IOException;
+
+/**
+ * @author Jon Brisbin
+ */
+public class JacksonJsonCodec<IN, OUT> extends SerializationCodec<ObjectMapper, IN, OUT> {
+
+	public JacksonJsonCodec() {
+		this(new ObjectMapper());
+	}
+
+	public JacksonJsonCodec(ObjectMapper engine) {
+		super(engine, true);
+	}
+
+	@Override
+	protected Function<byte[], IN> deserializer(final ObjectMapper engine,
+	                                            final Class<IN> type,
+	                                            final Consumer<IN> next) {
+		return new Function<byte[], IN>() {
+			@Override
+			public IN apply(byte[] bytes) {
+				try {
+					IN o = engine.readValue(bytes, type);
+					if(null != next) {
+						next.accept(o);
+						return null;
+					} else {
+						return o;
+					}
+				} catch(IOException e) {
+					throw new IllegalStateException(e.getMessage(), e);
+				}
+			}
+		};
+	}
+
+	@Override
+	protected Function<OUT, byte[]> serializer(final ObjectMapper engine) {
+		return new Function<OUT, byte[]>() {
+			@Override
+			public byte[] apply(OUT o) {
+				try {
+					return engine.writeValueAsBytes(o);
+				} catch(JsonProcessingException e) {
+					throw new IllegalArgumentException(e.getMessage(), e);
+				}
+			}
+		};
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/json/JsonCodec.java b/reactor-core/src/main/java/reactor/io/encoding/json/JsonCodec.java
new file mode 100644
index 0000000..0c704aa
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/json/JsonCodec.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io.encoding.json;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.util.Assert;
+
+import java.io.IOException;
+
+/**
+ * A codec for decoding JSON into Java objects and encoding Java objects into JSON.
+ *
+ * @param <IN>
+ * 		The type to decode JSON into
+ * @param <OUT>
+ * 		The type to encode into JSON
+ *
+ * @author Jon Brisbin
+ */
+public class JsonCodec<IN, OUT> implements Codec<Buffer, IN, OUT> {
+
+	private final Class<IN>    inputType;
+	private final ObjectMapper mapper;
+
+	/**
+	 * Creates a new {@code JsonCodec} that will create instances of {@code inputType}  when
+	 * decoding.
+	 *
+	 * @param inputType
+	 * 		The type to create when decoding.
+	 */
+	public JsonCodec(Class<IN> inputType) {
+		this(inputType, null);
+	}
+
+	/**
+	 * Creates a new {@code JsonCodec} that will create instances of {@code inputType}  when
+	 * decoding. The {@code customModule} will be registered with the underlying {@link
+	 * ObjectMapper}.
+	 *
+	 * @param inputType
+	 * 		The type to create when decoding.
+	 * @param customModule
+	 * 		The module to register with the underlying ObjectMapper
+	 */
+	@SuppressWarnings("unchecked")
+	public JsonCodec(Class<IN> inputType, Module customModule) {
+		Assert.notNull(inputType, "inputType must not be null");
+		this.inputType = (null == inputType ? (Class<IN>)JsonNode.class : inputType);
+
+		this.mapper = new ObjectMapper();
+		if(null != customModule) {
+			this.mapper.registerModule(customModule);
+		}
+	}
+
+	@Override
+	public Function<Buffer, IN> decoder(Consumer<IN> next) {
+		return new JsonDecoder(next);
+	}
+
+	@Override
+	public Function<OUT, Buffer> encoder() {
+		return new JsonEncoder();
+	}
+
+	private class JsonDecoder implements Function<Buffer, IN> {
+		private final Consumer<IN> next;
+
+		private JsonDecoder(Consumer<IN> next) {
+			this.next = next;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public IN apply(Buffer buffer) {
+			IN in;
+			try {
+				if(JsonNode.class.isAssignableFrom(inputType)) {
+					in = (IN)mapper.readTree(buffer.inputStream());
+				} else {
+					in = mapper.readValue(buffer.inputStream(), inputType);
+				}
+				if(null != next) {
+					next.accept(in);
+					return null;
+				} else {
+					return in;
+				}
+			} catch(IOException e) {
+				throw new IllegalStateException(e);
+			}
+		}
+	}
+
+	private class JsonEncoder implements Function<OUT, Buffer> {
+		@Override
+		public Buffer apply(OUT out) {
+			try {
+				return Buffer.wrap(mapper.writeValueAsBytes(out));
+			} catch(JsonProcessingException e) {
+				throw new IllegalStateException(e);
+			}
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/json/package-info.java b/reactor-core/src/main/java/reactor/io/encoding/json/package-info.java
new file mode 100644
index 0000000..650047d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/json/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Encoding and decoding using the JSON format.
+ */
+package reactor.io.encoding.json;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/io/encoding/kryo/KryoCodec.java b/reactor-core/src/main/java/reactor/io/encoding/kryo/KryoCodec.java
new file mode 100644
index 0000000..fda98ca
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/kryo/KryoCodec.java
@@ -0,0 +1,55 @@
+package reactor.io.encoding.kryo;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.UnsafeMemoryInput;
+import com.esotericsoftware.kryo.io.UnsafeMemoryOutput;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.io.encoding.SerializationCodec;
+
+/**
+ * @author Jon Brisbin
+ */
+public class KryoCodec<IN, OUT> extends SerializationCodec<Kryo, IN, OUT> {
+
+	public KryoCodec() {
+		super(new Kryo(), true);
+	}
+
+	public KryoCodec(Kryo engine, boolean lengthFieldFraming) {
+		super(engine, lengthFieldFraming);
+	}
+
+	@Override
+	protected Function<byte[], IN> deserializer(final Kryo engine,
+	                                            final Class<IN> type,
+	                                            final Consumer<IN> next) {
+		return new Function<byte[], IN>() {
+			@Override
+			public IN apply(byte[] bytes) {
+				IN obj = engine.readObject(new UnsafeMemoryInput(bytes), type);
+				if(null != next) {
+					next.accept(obj);
+					return null;
+				} else {
+					return obj;
+				}
+			}
+		};
+	}
+
+	@Override
+	protected Function<OUT, byte[]> serializer(final Kryo engine) {
+		return new Function<OUT, byte[]>() {
+			@Override
+			public byte[] apply(OUT o) {
+				UnsafeMemoryOutput out = new UnsafeMemoryOutput(Buffer.SMALL_BUFFER_SIZE, Buffer.MAX_BUFFER_SIZE);
+				engine.writeObject(out, o);
+				out.flush();
+				return out.toBytes();
+			}
+		};
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/encoding/package-info.java b/reactor-core/src/main/java/reactor/io/encoding/package-info.java
new file mode 100644
index 0000000..3c48aee
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Components to handle encoding and decoding of objects into {@link reactor.io.Buffer Buffers}.
+ */
+package reactor.io.encoding;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/io/encoding/protobuf/ProtobufCodec.java b/reactor-core/src/main/java/reactor/io/encoding/protobuf/ProtobufCodec.java
new file mode 100644
index 0000000..c8db8c1
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/encoding/protobuf/ProtobufCodec.java
@@ -0,0 +1,75 @@
+package reactor.io.encoding.protobuf;
+
+import com.google.protobuf.Message;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.encoding.SerializationCodec;
+import reactor.util.Assert;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ProtobufCodec<IN, OUT> extends SerializationCodec<Map<Class<?>, Message>, IN, OUT> {
+
+	public ProtobufCodec() {
+		this(true);
+	}
+
+	public ProtobufCodec(boolean lengthFieldFraming) {
+		super(new ConcurrentHashMap<Class<?>, Message>(), lengthFieldFraming);
+	}
+
+	@Override
+	protected Function<byte[], IN> deserializer(final Map<Class<?>, Message> messages,
+	                                            final Class<IN> type,
+	                                            final Consumer<IN> next) {
+		Assert.isAssignable(Message.class,
+		                    type,
+		                    "Can only deserialize Protobuf messages. " +
+				                    type.getName() +
+				                    " is not an instance of " +
+				                    Message.class.getName());
+		return new Function<byte[], IN>() {
+			@SuppressWarnings("unchecked")
+			@Override
+			public IN apply(byte[] bytes) {
+				try {
+					Message msg = messages.get(type);
+					if(null == msg) {
+						msg = (Message)type.getMethod("getDefaultInstance").invoke(null);
+						messages.put(type, msg);
+					}
+					IN obj = (IN)msg.newBuilderForType().mergeFrom(bytes).build();
+					if(null != next) {
+						next.accept(obj);
+						return null;
+					} else {
+						return obj;
+					}
+				} catch(Exception e) {
+					throw new IllegalStateException(e.getMessage(), e);
+				}
+			}
+		};
+	}
+
+	@Override
+	protected Function<OUT, byte[]> serializer(final Map<Class<?>, Message> messages) {
+		return new Function<OUT, byte[]>() {
+			@Override
+			public byte[] apply(Object o) {
+				Assert.isInstanceOf(Message.class,
+				                    o,
+				                    "Can only serialize Protobuf messages. " +
+						                    o.getClass().getName() +
+						                    " is not an instance of " +
+						                    Message.class.getName());
+				return ((Message)o).toByteArray();
+			}
+		};
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/io/package-info.java b/reactor-core/src/main/java/reactor/io/package-info.java
new file mode 100644
index 0000000..fd7ceca
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Buffer-handling and other IO-related components.
+ */
+package reactor.io;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/io/selector/JsonPathSelector.java b/reactor-core/src/main/java/reactor/io/selector/JsonPathSelector.java
new file mode 100644
index 0000000..a7e195b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/io/selector/JsonPathSelector.java
@@ -0,0 +1,295 @@
+package reactor.io.selector;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.type.CollectionType;
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.Filter;
+import com.jayway.jsonpath.InvalidJsonException;
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.internal.Utils;
+import com.jayway.jsonpath.spi.JsonProvider;
+import com.jayway.jsonpath.spi.MappingProvider;
+import com.jayway.jsonpath.spi.Mode;
+import reactor.event.selector.ObjectSelector;
+import reactor.event.selector.Selector;
+import reactor.io.Buffer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * @author Jon Brisbin
+ */
+public class JsonPathSelector extends ObjectSelector<JsonPath> {
+
+	// Only need one of these
+	private static ObjectMapper MAPPER = new ObjectMapper();
+
+	private final ObjectMapper  mapper;
+	private final Configuration jsonPathConfig;
+
+	public JsonPathSelector(ObjectMapper mapper, String jsonPath, Filter... filters) {
+		super(JsonPath.compile(jsonPath, filters));
+		this.mapper = mapper;
+		this.jsonPathConfig = Configuration.builder().jsonProvider(new Jackson2JsonProvider(mapper)).build();
+	}
+
+	public JsonPathSelector(String jsonPath, Filter... filters) {
+		this(MAPPER, jsonPath, filters);
+	}
+
+	public static Selector J(String jsonPath, Filter... filters) {
+		return jsonPathSelector(jsonPath, filters);
+	}
+
+	public static Selector jsonPathSelector(String jsonPath, Filter... filters) {
+		return new JsonPathSelector(jsonPath, filters);
+	}
+
+	@Override
+	public boolean matches(Object key) {
+		if(null == key) {
+			return false;
+		}
+
+		Object result = read(key);
+		if(null == result) {
+			return false;
+		}
+		Class<?> type = result.getClass();
+		if(Collection.class.isAssignableFrom(type)) {
+			return ((Collection)result).size() > 0;
+		} else if(Map.class.isAssignableFrom(type)) {
+			return ((Map)result).size() > 0;
+		} else if(JsonNode.class.isAssignableFrom(type)) {
+			return ((JsonNode)result).size() > 0;
+		} else {
+			return true;
+		}
+	}
+
+	private Object read(Object key) {
+		Class<?> type = key.getClass();
+		if(type == String.class) {
+			return getObject().read((String)key, jsonPathConfig);
+		} else if(type == byte[].class) {
+			return getObject().read(Buffer.wrap((byte[])key).asString(), jsonPathConfig);
+		} else if(type == Buffer.class) {
+			return getObject().read(((Buffer)key).asString(), jsonPathConfig);
+		} else if(JsonNode.class.isAssignableFrom(type)) {
+			return getObject().read(key, jsonPathConfig);
+		} else {
+			return getObject().read(mapper.convertValue(key, Object.class), jsonPathConfig);
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private static class Jackson2JsonProvider implements JsonProvider, MappingProvider {
+		private final ObjectMapper mapper;
+
+		private Jackson2JsonProvider(ObjectMapper mapper) {
+			this.mapper = mapper;
+		}
+
+		@Override
+		public Mode getMode() {
+			return Mode.STRICT;
+		}
+
+		@Override
+		public Object parse(String json) throws InvalidJsonException {
+			try {
+				return mapper.readValue(json, Object.class);
+			} catch(IOException e) {
+				throw new InvalidJsonException(e.getMessage(), e);
+			}
+		}
+
+		@Override
+		public Object parse(Reader jsonReader) throws InvalidJsonException {
+			try {
+				return mapper.readValue(jsonReader, Object.class);
+			} catch(IOException e) {
+				throw new InvalidJsonException(e.getMessage(), e);
+			}
+		}
+
+		@Override
+		public Object parse(InputStream jsonStream) throws InvalidJsonException {
+			try {
+				return mapper.readValue(jsonStream, Object.class);
+			} catch(IOException e) {
+				throw new InvalidJsonException(e.getMessage(), e);
+			}
+		}
+
+		@Override
+		public String toJson(Object obj) {
+			try {
+				return mapper.writeValueAsString(obj);
+			} catch(JsonProcessingException e) {
+				throw new IllegalStateException(e.getMessage(), e);
+			}
+		}
+
+		@Override
+		public Object createMap() {
+			return new HashMap();
+		}
+
+		@Override
+		public Iterable createArray() {
+			return new ArrayList();
+		}
+
+		@Override
+		public <T> T convertValue(Object fromValue,
+		                          Class<T> toValueType)
+				throws IllegalArgumentException {
+			return mapper.convertValue(fromValue, toValueType);
+		}
+
+		@Override
+		public <T extends Collection<E>, E> T convertValue(Object fromValue,
+		                                                   Class<T> collectionType,
+		                                                   Class<E> elementType)
+				throws IllegalArgumentException {
+			CollectionType collType = mapper.getTypeFactory().constructCollectionType(collectionType, elementType);
+			return mapper.convertValue(fromValue, collType);
+		}
+
+		@Override
+		public Object clone(Object model) {
+			return Utils.clone((Serializable)model);
+		}
+
+		@Override
+		public boolean isContainer(Object obj) {
+			return (isMap(obj) || isArray(obj));
+		}
+
+		@Override
+		public boolean isArray(Object obj) {
+			return (obj instanceof List || obj instanceof ArrayNode);
+		}
+
+		@Override
+		public boolean isMap(Object obj) {
+			return (obj instanceof Map || obj instanceof ObjectNode);
+		}
+
+		@Override
+		public int length(Object obj) {
+			if(obj instanceof List) {
+				return ((List)obj).size();
+			} else if(obj instanceof Map) {
+				return ((Map)obj).size();
+			} else if(obj instanceof ArrayNode) {
+				return ((ArrayNode)obj).size();
+			} else if(obj instanceof ObjectNode) {
+				return ((ObjectNode)obj).size();
+			} else {
+				return length(mapper.convertValue(obj, JsonNode.class));
+			}
+		}
+
+		@Override
+		public Iterable<Object> toIterable(Object obj) {
+			if(obj instanceof List || obj instanceof JsonNode) {
+				return (Iterable<Object>)obj;
+			} else if(obj instanceof Map) {
+				return ((Map)obj).values();
+			} else {
+				return toIterable(mapper.convertValue(obj, JsonNode.class));
+			}
+		}
+
+		@Override
+		public Collection<String> getPropertyKeys(Object obj) {
+			if(obj instanceof Map) {
+				return ((Map)obj).keySet();
+			} else if(obj instanceof List) {
+				List l = (List)obj;
+				List<String> keys = new ArrayList<String>(l.size());
+				for(Object o : l) {
+					keys.add(String.valueOf(o));
+				}
+				return keys;
+			} else if(obj instanceof ObjectNode) {
+				ObjectNode node = (ObjectNode)obj;
+				List<String> keys = new ArrayList<String>(node.size());
+				Iterator<String> iter = node.fieldNames();
+				while(iter.hasNext()) {
+					keys.add(iter.next());
+				}
+				return keys;
+			} else if(obj instanceof ArrayNode) {
+				ArrayNode node = (ArrayNode)obj;
+				List<String> keys = new ArrayList<String>(node.size());
+				int len = node.size();
+				for(int i = 0; i < len; i++) {
+					keys.add(String.valueOf(node.get(i)));
+				}
+				return keys;
+			} else {
+				return getPropertyKeys(mapper.convertValue(obj, JsonNode.class));
+			}
+		}
+
+		@Override
+		public Object getProperty(Object obj, Object key) {
+			if(obj instanceof Map) {
+				return ((Map)obj).get(key);
+			} else if(obj instanceof List) {
+				int idx = key instanceof Integer ? (Integer)key : Integer.parseInt(key.toString());
+				return ((List)obj).get(idx);
+			} else if(obj instanceof ObjectNode) {
+				return unwrap(((ObjectNode)obj).get(key.toString()));
+			} else if(obj instanceof ArrayNode) {
+				int idx = key instanceof Integer ? (Integer)key : Integer.parseInt(key.toString());
+				return unwrap(((ArrayNode)obj).get(idx));
+			} else {
+				return getProperty(mapper.convertValue(obj, JsonNode.class), key);
+			}
+		}
+
+		@Override
+		public void setProperty(Object obj, Object key, Object value) {
+			if(obj instanceof Map) {
+				((Map)obj).put(key, value);
+			} else if(obj instanceof List) {
+				int idx = key instanceof Integer ? (Integer)key : Integer.parseInt(key.toString());
+				((List)obj).add(idx, value);
+			} else if(obj instanceof ObjectNode) {
+				((ObjectNode)obj).set(key.toString(), (JsonNode)value);
+			} else if(obj instanceof ArrayNode) {
+				int idx = key instanceof Integer ? (Integer)key : Integer.parseInt(key.toString());
+				((ArrayNode)obj).set(idx, (JsonNode)value);
+			} else {
+				setProperty(mapper.convertValue(obj, JsonNode.class), key, value);
+			}
+		}
+
+		private Object unwrap(JsonNode node) {
+			if(node.isValueNode()) {
+				if(node.isNumber()) {
+					return node.numberValue();
+				} else if(node.isTextual()) {
+					return node.asText();
+				} else {
+					return node;
+				}
+			} else {
+				return node;
+			}
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/pool/LoadingPool.java b/reactor-core/src/main/java/reactor/pool/LoadingPool.java
new file mode 100644
index 0000000..372075b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/pool/LoadingPool.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.pool;
+
+import reactor.function.Supplier;
+import reactor.queue.BlockingQueueFactory;
+
+import javax.annotation.Nullable;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link Pool} implementaion that uses a {@link Supplier} to create the cached objects.
+ * The cache is preloaded with a configurable number of instances. In the event of an
+ * object not being available to meet an allocation request a new object will be created.
+ *
+ * @param <T> The type of objects held by the cache
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+ at Deprecated
+public class LoadingPool<T> implements Pool<T> {
+
+	private final BlockingQueue<T> cache = BlockingQueueFactory.createQueue();
+	private final Supplier<T> supplier;
+	private final Supplier<Long> currentTimeMillis;
+	private final long        cacheMissTimeout;
+
+	/**
+	 * Creates a new {@code LoadingPool} that will use the {@code supplier} to
+	 *
+	 * @param supplier The Supplier used to create objects
+	 * @param initial The number of objects to prepopulate the cache with
+	 * @param cacheMissTimeout The period of time to wait for an object to be available before
+	 *                         using the supplier to create a new one
+	 */
+	public LoadingPool(Supplier<T> supplier, Supplier<Long> currentTimeMillis, int initial, long cacheMissTimeout) {
+		this.supplier = supplier;
+		this.currentTimeMillis=currentTimeMillis;
+		this.cacheMissTimeout = cacheMissTimeout;
+
+		for (int i = 0; i < initial; i++) {
+			this.cache.add(supplier.get());
+		}
+	}
+
+	@Nullable
+	@Override
+	public T allocate() {
+		T obj;
+		try {
+			long start = currentTimeMillis.get();
+			do {
+				obj = cache.poll(cacheMissTimeout, TimeUnit.MILLISECONDS);
+			} while (null == obj && (currentTimeMillis.get() - start) < cacheMissTimeout);
+			return (null != obj ? obj : supplier.get());
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+			return supplier.get();
+		}
+	}
+
+	@Override
+	public void deallocate(T obj) {
+		cache.add(obj);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/pool/Pool.java b/reactor-core/src/main/java/reactor/pool/Pool.java
new file mode 100644
index 0000000..5f02db2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/pool/Pool.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.pool;
+
+/**
+ * A {@code Pool} provides access to a pool of objects.
+ *
+ * @param <T>
+ * 		The type of object in the pool
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @since 1.1
+ */
+ at Deprecated
+public interface Pool<T> {
+
+	/**
+	 * Allocates a new object from the pool.
+	 *
+	 * @return the allocated object
+	 */
+	T allocate();
+
+	/**
+	 * Returns an object to the pool.
+	 *
+	 * @param obj
+	 * 		The object to deallocate
+	 */
+	void deallocate(T obj);
+
+}
diff --git a/reactor-core/src/main/java/reactor/queue/BlockingQueueFactory.java b/reactor-core/src/main/java/reactor/queue/BlockingQueueFactory.java
new file mode 100644
index 0000000..cc046b4
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/queue/BlockingQueueFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.queue;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.LinkedTransferQueue;
+
+/**
+ * A factory for creating {@link BlockingQueue} instances. When available, {@link
+ * LinkedTransferQueue}s will be created, otherwise {@link LinkedBlockingQueue}s will be
+ * created.
+ *
+ * @author Stephane Maldini
+ */
+ at SuppressWarnings({"unchecked", "rawtypes"})
+public class BlockingQueueFactory {
+
+	static {
+		try {
+			QUEUE_TYPE = (Class<? extends BlockingQueue>) Class.forName("java.util.concurrent.LinkedTransferQueue");
+		} catch (ClassNotFoundException e) {
+			QUEUE_TYPE = LinkedBlockingQueue.class;
+		}
+	}
+
+	private static Class<? extends BlockingQueue> QUEUE_TYPE;
+
+	/**
+	 * Creates a new {@link BlockingQueue}
+	 *
+	 * @param <D> the type of values that the queue will hold
+	 *
+	 * @return the blocking queue
+	 */
+	public static <D> BlockingQueue<D> createQueue() {
+		try {
+			return (BlockingQueue<D>) QUEUE_TYPE.newInstance();
+		} catch (Throwable t) {
+			return new LinkedBlockingQueue<D>();
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/queue/InMemoryQueuePersistor.java b/reactor-core/src/main/java/reactor/queue/InMemoryQueuePersistor.java
new file mode 100644
index 0000000..9f6e5db
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/queue/InMemoryQueuePersistor.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.queue;
+
+import reactor.function.Function;
+import reactor.function.Supplier;
+
+import javax.annotation.Nonnull;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A {@link QueuePersistor} implementations that stores items in-memory.
+ *
+ * @author Jon Brisbin
+ */
+public class InMemoryQueuePersistor<T> implements QueuePersistor<T> {
+
+	private final Map<Long, T> objects   = Collections.synchronizedMap(new HashMap<Long, T>());
+	private final AtomicLong   counter   = new AtomicLong();
+	private final AtomicLong   currentId = new AtomicLong();
+	private final Function<T, Long> offerFun;
+	private final Function<Long, T> getFun;
+	private final Supplier<T>       removeFun;
+
+	public InMemoryQueuePersistor() {
+		this.offerFun = new MapOfferFunction();
+		this.getFun = new MapGetFunction();
+		this.removeFun = new MapRemoveFunction();
+	}
+
+	@Override
+	public long lastId() {
+		return currentId.get();
+	}
+
+	@Override
+	public long size() {
+		return objects.size();
+	}
+
+	@Override
+	public boolean hasNext() {
+		return !objects.isEmpty();
+	}
+
+	@Override
+	public Iterator<T> iterator() {
+		return objects.values().iterator();
+	}
+
+	@Nonnull
+	@Override
+	public Function<T, Long> offer() {
+		return offerFun;
+	}
+
+	@Nonnull
+	@Override
+	public Function<Long, T> get() {
+		return getFun;
+	}
+
+	@Nonnull
+	@Override
+	public Supplier<T> remove() {
+		return removeFun;
+	}
+
+	@Override
+	public void close() {
+	}
+
+	private class MapOfferFunction implements Function<T, Long> {
+		@Override
+		public Long apply(T obj) {
+			Long id = counter.getAndIncrement();
+			objects.put(id, obj);
+			return id;
+		}
+	}
+
+	private class MapGetFunction implements Function<Long, T> {
+		@Override
+		public T apply(Long l) {
+			return objects.get(l);
+		}
+	}
+
+	private class MapRemoveFunction implements Supplier<T> {
+		@Override
+		public T get() {
+			Long id = currentId.getAndIncrement();
+			return objects.remove(id);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/queue/IndexedChronicleQueuePersistor.java b/reactor-core/src/main/java/reactor/queue/IndexedChronicleQueuePersistor.java
new file mode 100644
index 0000000..0d8ef5a
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/queue/IndexedChronicleQueuePersistor.java
@@ -0,0 +1,264 @@
+package reactor.queue;
+
+import net.openhft.chronicle.*;
+import net.openhft.chronicle.tools.ChronicleTools;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.function.Function;
+import reactor.function.Supplier;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.io.encoding.JavaSerializationCodec;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A {@link QueuePersistor} implementation that uses a <a href="https://github.com/peter-lawrey/Java-Chronicle">Java
+ * Chronicle</a> {@literal IndexedChronicle} to persist items in the queue.
+ *
+ * @author Jon Brisbin
+ * @see <a href="https://github.com/peter-lawrey/Java-Chronicle">Java Chronicle</a>
+ */
+public class IndexedChronicleQueuePersistor<T> implements QueuePersistor<T> {
+
+	private static final Logger LOG = LoggerFactory.getLogger(IndexedChronicleQueuePersistor.class);
+
+	private final Object     monitor = new Object();
+	private final AtomicLong lastId  = new AtomicLong();
+	private final AtomicLong size    = new AtomicLong(0);
+
+	private final String                  basePath;
+	private final Codec<Buffer, T, T>     codec;
+	private final boolean                 deleteOnExit;
+	private final IndexedChronicle        data;
+	private final ChronicleOfferFunction  offerFun;
+	private final ChronicleGetFunction    getFun;
+	private final ChronicleRemoveFunction removeFun;
+
+	/**
+	 * Create an {@link IndexedChronicleQueuePersistor} based on the given base path.
+	 *
+	 * @param basePath
+	 * 		Directory in which to create the Chronicle.
+	 *
+	 * @throws IOException
+	 */
+	public IndexedChronicleQueuePersistor(@Nonnull String basePath) throws IOException {
+		this(basePath, new JavaSerializationCodec<T>(), false, true, ChronicleConfig.DEFAULT.clone());
+	}
+
+	/**
+	 * Create an {@link IndexedChronicleQueuePersistor} based on the given base path, encoder and decoder. Optionally,
+	 * passing {@literal false} to {@code clearOnStart} skips clearing the Chronicle on start for appending.
+	 *
+	 * @param basePath
+	 * 		Directory in which to create the Chronicle.
+	 * @param codec
+	 * 		Codec to turn objects into {@link reactor.io.Buffer Buffers} and visa-versa.
+	 * @param clearOnStart
+	 * 		Whether or not to clear the Chronicle on start.
+	 * @param deleteOnExit
+	 * 		Whether or not to delete the Chronicle when the program exits.
+	 * @param config
+	 * 		ChronicleConfig to use.
+	 *
+	 * @throws IOException
+	 */
+	public IndexedChronicleQueuePersistor(@Nonnull String basePath,
+																				@Nonnull Codec<Buffer, T, T> codec,
+																				boolean clearOnStart,
+																				boolean deleteOnExit,
+																				@Nonnull ChronicleConfig config) throws IOException {
+		this.basePath = basePath;
+		this.codec = codec;
+		this.deleteOnExit = deleteOnExit;
+
+		if(clearOnStart) {
+			for(String name : new String[]{basePath + ".data", basePath + ".index"}) {
+				File file = new File(name);
+				if(file.exists()) {
+					file.delete();
+				}
+			}
+		}
+
+		ChronicleTools.warmup();
+		data = new IndexedChronicle(basePath, config);
+		lastId.set(data.findTheLastIndex());
+
+		Excerpt ex = data.createExcerpt();
+		while(ex.nextIndex()) {
+			int len = ex.readInt();
+			size.incrementAndGet();
+			ex.skip(len);
+		}
+
+		offerFun = new ChronicleOfferFunction();
+		getFun = new ChronicleGetFunction();
+		removeFun = new ChronicleRemoveFunction(data.createTailer());
+	}
+
+	/**
+	 * Close the underlying chronicles.
+	 */
+	@Override
+	public void close() {
+		try {
+			data.close();
+
+			if(deleteOnExit) {
+				ChronicleTools.deleteOnExit(basePath);
+			}
+		} catch(IOException e) {
+			throw new IllegalStateException(e.getMessage(), e);
+		}
+	}
+
+	@Override
+	public long lastId() {
+		return lastId.get();
+	}
+
+	@Override
+	public long size() {
+		return size.get();
+	}
+
+	@Override
+	public boolean hasNext() {
+		return removeFun.hasNext();
+	}
+
+	@Nonnull
+	@Override
+	public Function<T, Long> offer() {
+		return offerFun;
+	}
+
+	@Nonnull
+	@Override
+	public Function<Long, T> get() {
+		return getFun;
+	}
+
+	@Nonnull
+	@Override
+	public Supplier<T> remove() {
+		return removeFun;
+	}
+
+	@Override
+	public Iterator<T> iterator() {
+		final ChronicleRemoveFunction fn;
+		try {
+			fn = new ChronicleRemoveFunction(data.createTailer());
+		} catch(IOException e) {
+			throw new IllegalStateException(e.getMessage(), e);
+		}
+
+		return new Iterator<T>() {
+			public boolean hasNext() {
+				return fn.hasNext();
+			}
+
+			@SuppressWarnings("unchecked")
+			@Override
+			public T next() {
+				return fn.get();
+			}
+
+			@Override
+			public void remove() {
+				throw new IllegalStateException("This Iterator is read-only.");
+			}
+		};
+	}
+
+	@SuppressWarnings("unchecked")
+	private T read(ExcerptCommon ex) {
+		try {
+			int len = ex.readInt();
+			ByteBuffer bb = ByteBuffer.allocate(len);
+			ex.read(bb);
+			bb.flip();
+			return codec.decoder(null).apply(new Buffer(bb));
+		} finally {
+			ex.finish();
+		}
+	}
+
+	private class ChronicleOfferFunction implements Function<T, Long> {
+		private final ExcerptAppender ex;
+
+		private ChronicleOfferFunction() throws IOException {
+			this.ex = data.createAppender();
+		}
+
+		@Override
+		public Long apply(T t) {
+			synchronized(monitor) {
+				Buffer buff = codec.encoder().apply(t);
+
+				int len = buff.remaining();
+				ex.startExcerpt(4 + len);
+				ex.writeInt(len);
+				ex.write(buff.byteBuffer());
+				ex.finish();
+
+				size.incrementAndGet();
+				lastId.set(ex.lastWrittenIndex());
+			}
+
+			if(LOG.isTraceEnabled()) {
+				LOG.trace("Offered {} to Chronicle at index {}, size {}", t, lastId(), size());
+			}
+
+			return lastId();
+		}
+	}
+
+	private class ChronicleGetFunction implements Function<Long, T> {
+		private final ExcerptTailer ex;
+
+		private ChronicleGetFunction() throws IOException {
+			this.ex = data.createTailer();
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public T apply(Long id) {
+			if(!ex.index(id)) {
+				return null;
+			}
+			return read(ex);
+		}
+	}
+
+	private class ChronicleRemoveFunction implements Supplier<T> {
+		private final ExcerptTailer ex;
+
+		private ChronicleRemoveFunction(ExcerptTailer ex) throws IOException {
+			this.ex = ex;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public T get() {
+			synchronized(monitor) {
+				T obj = read(ex);
+				size.decrementAndGet();
+				return obj;
+			}
+		}
+
+		public boolean hasNext() {
+			return ex.nextIndex();
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/queue/PersistentQueue.java b/reactor-core/src/main/java/reactor/queue/PersistentQueue.java
new file mode 100644
index 0000000..42ac391
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/queue/PersistentQueue.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.queue;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.AbstractQueue;
+import java.util.Iterator;
+
+/**
+ * A {@literal PersistentQueue} is a {@link java.util.Queue} implementation that delegates the actual storage of the
+ * elements in the queue to a {@link QueuePersistor}.
+ *
+ * @author Jon Brisbin
+ */
+public class PersistentQueue<T> extends AbstractQueue<T> {
+
+	private final QueuePersistor<T> persistor;
+
+	/**
+	 * Create a {@literal PersistentQueue} using the given {@link QueuePersistor}.
+	 *
+	 * @param persistor
+	 */
+	public PersistentQueue(@Nullable QueuePersistor<T> persistor) {
+		this.persistor = (null == persistor ? new InMemoryQueuePersistor<T>() : persistor);
+	}
+
+	/**
+	 * Close the underlying {@link reactor.queue.QueuePersistor} and release any resources.
+	 */
+	public void close() {
+		persistor.close();
+	}
+
+	@Nonnull
+	public Iterator<T> iterator() {
+		return persistor.iterator();
+	}
+
+	@Override
+	public int size() {
+		return (int)persistor.size();
+	}
+
+	@Override
+	public boolean offer(T obj) {
+		return (null != persistor.offer().apply(obj));
+	}
+
+	@Override
+	public T poll() {
+		if(size() == 0 || !persistor.hasNext()) {
+			return null;
+		}
+		return persistor.remove().get();
+	}
+
+	@Override
+	public T peek() {
+		if(size() == 0 || !persistor.hasNext()) {
+			return null;
+		}
+		Long lastId = persistor.lastId();
+		return persistor.get().apply(lastId);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/queue/QueuePersistor.java b/reactor-core/src/main/java/reactor/queue/QueuePersistor.java
new file mode 100644
index 0000000..2e8fceb
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/queue/QueuePersistor.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.queue;
+
+import reactor.function.Function;
+import reactor.function.Supplier;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Implementations of this interface are responsible for persisting the elements of a {@link PersistentQueue}.
+ * Persistence could be achieved through in-memory solutions like a {@link java.util.Map} or could be more complex and
+ * use a backing datastore.
+ *
+ * @author Jon Brisbin
+ */
+public interface QueuePersistor<T> extends Iterable<T> {
+
+	/**
+	 * Get the value of the last item to have been persisted.
+	 *
+	 * @return last ID persisted
+	 */
+	long lastId();
+
+	/**
+	 * Get the number of items persisted right now.
+	 *
+	 * @return number of items persisted
+	 */
+	long size();
+
+	/**
+	 * Are there more items waiting to be returned?
+	 *
+	 * @return {@code true} if items can be retrieved from this persistor, {@code false} otherwise
+	 */
+	boolean hasNext();
+
+	/**
+	 * Returns a {@link Function} that will persist the item and return a Long id for the item.
+	 *
+	 * @return id of the item just persisted
+	 */
+	@Nonnull
+	Function<T, Long> offer();
+
+	/**
+	 * Returns a {@link Function} that will return the item with the given id.
+	 *
+	 * @return item persisted under the given id
+	 */
+	@Nonnull
+	Function<Long, T> get();
+
+	/**
+	 * Returns a {@link Supplier} that will simply remove the oldest item from the persistence queue.
+	 *
+	 * @return the oldest item in the queue
+	 */
+	@Nonnull
+	Supplier<T> remove();
+
+	/**
+	 * Release any internal resources used by the persistence mechanism.
+	 */
+	void close();
+
+}
diff --git a/reactor-core/src/main/java/reactor/queue/package-info.java b/reactor-core/src/main/java/reactor/queue/package-info.java
new file mode 100644
index 0000000..aca1b4d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/queue/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * {@link java.util.Queue} implementations that provide a persistence strategy for making sure items in the {@code
+ * Queue} aren't lost.
+ */
+package reactor.queue;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/queue/spec/PersistentQueueSpec.java b/reactor-core/src/main/java/reactor/queue/spec/PersistentQueueSpec.java
new file mode 100644
index 0000000..f70d1c1
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/queue/spec/PersistentQueueSpec.java
@@ -0,0 +1,88 @@
+package reactor.queue.spec;
+
+import net.openhft.chronicle.ChronicleConfig;
+import reactor.function.Supplier;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.queue.IndexedChronicleQueuePersistor;
+import reactor.queue.PersistentQueue;
+
+import java.io.IOException;
+
+/**
+ * Helper spec to create a {@link PersistentQueue} instance.
+ *
+ * @author Jon Brisbin
+ */
+public class PersistentQueueSpec<T> implements Supplier<PersistentQueue<T>> {
+
+	private String  basePath     = System.getProperty("java.io.tmpdir") + "/persistent-queue";
+	private boolean clearOnStart = false;
+	private boolean deleteOnExit = false;
+	private Codec<Buffer, T, T> codec;
+	private ChronicleConfig config = ChronicleConfig.DEFAULT.clone();
+
+	public PersistentQueueSpec<T> codec(Codec<Buffer, T, T> codec) {
+		this.codec = codec;
+		return this;
+	}
+
+	public PersistentQueueSpec<T> basePath(String basePath) {
+		this.basePath = basePath;
+		return this;
+	}
+
+	public PersistentQueueSpec<T> clearOnStart(boolean clearOnStart) {
+		this.clearOnStart = clearOnStart;
+		return this;
+	}
+
+	public PersistentQueueSpec<T> deleteOnExit(boolean deleteOnExit) {
+		this.deleteOnExit = deleteOnExit;
+		return this;
+	}
+
+	public PersistentQueueSpec<T> cacheLineSize(int size) {
+		config.cacheLineSize(size);
+		return this;
+	}
+
+	public PersistentQueueSpec<T> dataBlockSize(int size) {
+		config.dataBlockSize(size);
+		return this;
+	}
+
+	public PersistentQueueSpec<T> indexFileCapacity(int size) {
+		config.indexFileCapacity(size);
+		return this;
+	}
+
+	public PersistentQueueSpec<T> indexFileCapacity(boolean synchronousMode) {
+		config.synchronousMode(synchronousMode);
+		return this;
+	}
+
+	public PersistentQueueSpec<T> indexFileExcerpts(int excerpts) {
+		config.indexFileExcerpts(excerpts);
+		return this;
+	}
+
+	public PersistentQueueSpec<T> minimiseFootprint(boolean minimiseFootprint) {
+		config.minimiseFootprint(minimiseFootprint);
+		return this;
+	}
+
+	@Override
+	public PersistentQueue<T> get() {
+		try {
+			return new PersistentQueue<T>(new IndexedChronicleQueuePersistor<T>(basePath,
+																																					codec,
+																																					clearOnStart,
+																																					deleteOnExit,
+																																					config));
+		} catch(IOException e) {
+			throw new IllegalStateException(e.getMessage(), e);
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/queue/spec/package-info.java b/reactor-core/src/main/java/reactor/queue/spec/package-info.java
new file mode 100644
index 0000000..fbf19a8
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/queue/spec/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Specs provide a simple fluent DSL for creating {@link reactor.queue.PersistentQueue PersistentQueues} by
+ * specifying common options.
+ */
+package reactor.queue.spec;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/support/Identifiable.java b/reactor-core/src/main/java/reactor/support/Identifiable.java
new file mode 100644
index 0000000..b96126e
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/support/Identifiable.java
@@ -0,0 +1,12 @@
+package reactor.support;
+
+/**
+ * @author Jon Brisbin
+ */
+public interface Identifiable<ID> {
+
+	Identifiable<ID> setId(ID id);
+
+	ID getId();
+
+}
diff --git a/reactor-core/src/main/java/reactor/support/NamedDaemonThreadFactory.java b/reactor-core/src/main/java/reactor/support/NamedDaemonThreadFactory.java
new file mode 100644
index 0000000..5e432e6
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/support/NamedDaemonThreadFactory.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.support;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A thread factory that creates named daemon threads. Each thread created by this class
+ * will have a different name due to a count of the number of threads  created thus far
+ * being included in the name of each thread. This count is held statically to ensure
+ * different thread names across different instances of this class.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @see Thread#setDaemon(boolean)
+ * @see Thread#setName(String)
+ */
+public class NamedDaemonThreadFactory implements ThreadFactory {
+
+	private static final AtomicInteger COUNTER = new AtomicInteger(0);
+
+	private final String                          prefix;
+	private final ClassLoader                     contextClassLoader;
+	private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
+
+	/**
+	 * Creates a new thread factory that will name its threads <prefix>-<n>, where
+	 * <prefix> is the given {@code prefix} and <n> is the count of threads
+	 * created thus far by this class.
+	 *
+	 * @param prefix
+	 * 		The thread name prefix
+	 */
+	public NamedDaemonThreadFactory(String prefix) {
+		this(prefix, null);
+	}
+
+	/**
+	 * Creates a new thread factory that will name its threads <prefix>-<n>, where
+	 * <prefix> is the given {@code prefix} and <n> is the count of threads
+	 * created thus far by this class. If the contextClassLoader parameter is not null it will assign it to the forged
+	 * Thread
+	 *
+	 * @param prefix
+	 * 		The thread name prefix
+	 * @param contextClassLoader
+	 * 		An optional classLoader to assign to thread
+	 */
+	public NamedDaemonThreadFactory(String prefix, ClassLoader contextClassLoader) {
+		this(prefix, contextClassLoader, null);
+	}
+
+	public NamedDaemonThreadFactory(String prefix,
+	                                ClassLoader contextClassLoader,
+	                                Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
+		this.prefix = prefix;
+		this.contextClassLoader = contextClassLoader;
+		this.uncaughtExceptionHandler = uncaughtExceptionHandler;
+	}
+
+	@Override
+	public Thread newThread(Runnable runnable) {
+		Thread t = new Thread(runnable);
+		t.setName(prefix + "-" + COUNTER.incrementAndGet());
+		t.setDaemon(true);
+		if(contextClassLoader != null) {
+			t.setContextClassLoader(contextClassLoader);
+		}
+		if(null != uncaughtExceptionHandler) {
+			t.setUncaughtExceptionHandler(uncaughtExceptionHandler);
+		}
+		return t;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/support/Supports.java b/reactor-core/src/main/java/reactor/support/Supports.java
new file mode 100644
index 0000000..8cd6468
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/support/Supports.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.support;
+
+/**
+ * Simple interface that can be applied to components to determine whether or not they support a particular type of
+ * use.
+ *
+ * @param <T> the type of values that may be supported
+ *
+ * @author Jon Brisbin
+ */
+public interface Supports<T> {
+
+	/**
+	 * Implementations should decided whether they support the given object or not.
+	 *
+	 * @param obj
+	 * 		The object that may or may not be supported.
+	 *
+	 * @return {@literal true} is this component can deal with the given object, {@literal false}, otherwise.
+	 */
+	boolean supports(T obj);
+
+}
diff --git a/reactor-core/src/main/java/reactor/timer/HashWheelTimer.java b/reactor-core/src/main/java/reactor/timer/HashWheelTimer.java
new file mode 100644
index 0000000..2f7f1c9
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/timer/HashWheelTimer.java
@@ -0,0 +1,490 @@
+package reactor.timer;
+
+import com.lmax.disruptor.EventFactory;
+import com.lmax.disruptor.RingBuffer;
+import reactor.event.lifecycle.Pausable;
+import reactor.event.registry.Registration;
+import reactor.event.selector.Selector;
+import reactor.function.Consumer;
+import reactor.support.NamedDaemonThreadFactory;
+import reactor.util.Assert;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Hash Wheel Timer, as per the paper:
+ *
+ * Hashed and hierarchical timing wheels:
+ * http://www.cs.columbia.edu/~nahum/w6998/papers/ton97-timing-wheels.pdf
+ *
+ * More comprehensive slides, explaining the paper can be found here:
+ * http://www.cse.wustl.edu/~cdgill/courses/cs6874/TimingWheels.ppt
+ *
+ * Hash Wheel timer is an approximated timer that allows performant execution of
+ * larger amount of tasks with better performance compared to traditional scheduling.
+ *
+ * @author Oleksandr Petrov
+ */
+public class HashWheelTimer implements Timer {
+
+	public static final  int    DEFAULT_WHEEL_SIZE = 512;
+	private static final String DEFAULT_TIMER_NAME = "hash-wheel-timer";
+
+	private final RingBuffer<Set<TimerRegistration>> wheel;
+	private final int                                resolution;
+	private final Thread                             loop;
+	private final Executor                           executor;
+	private final WaitStrategy                       waitStrategy;
+
+	/**
+	 * Create a new {@code HashWheelTimer} using the given with default resolution of 100 milliseconds and
+	 * default wheel size.
+	 */
+	public HashWheelTimer() {
+		this(100, DEFAULT_WHEEL_SIZE, new SleepWait());
+	}
+
+	/**
+	 * Create a new {@code HashWheelTimer} using the given timer resolution. All times will rounded up to the closest
+	 * multiple of this resolution.
+	 *
+	 * @param resolution
+	 * 		the resolution of this timer, in milliseconds
+	 */
+	public HashWheelTimer(int resolution) {
+		this(resolution, DEFAULT_WHEEL_SIZE, new SleepWait());
+	}
+
+	/**
+	 * Create a new {@code HashWheelTimer} using the given timer {@data resolution} and {@data wheelSize}. All times will
+	 * rounded up to the closest multiple of this resolution.
+	 *
+	 * @param res
+	 * 		resolution of this timer in milliseconds
+	 * @param wheelSize
+	 * 		size of the Ring Buffer supporting the Timer, the larger the wheel, the less the lookup time is
+	 * 		for sparse timeouts. Sane default is 512.
+	 * @param waitStrategy
+	 * 		strategy for waiting for the next tick
+	 */
+	public HashWheelTimer(int res, int wheelSize, WaitStrategy waitStrategy) {
+		this(DEFAULT_TIMER_NAME, res, wheelSize, waitStrategy, Executors.newFixedThreadPool(1));
+	}
+
+	/**
+	 * Create a new {@code HashWheelTimer} using the given timer {@data resolution} and {@data wheelSize}. All times will
+	 * rounded up to the closest multiple of this resolution.
+	 *
+	 * @param name
+	 * 		name for daemon thread factory to be displayed
+	 * @param res
+	 * 		resolution of this timer in milliseconds
+	 * @param wheelSize
+	 * 		size of the Ring Buffer supporting the Timer, the larger the wheel, the less the lookup time is
+	 * 		for sparse timeouts. Sane default is 512.
+	 * @param strategy
+	 * 		strategy for waiting for the next tick
+	 * @param exec
+	 * 		Executor instance to submit tasks to
+	 */
+	public HashWheelTimer(String name, int res, int wheelSize, WaitStrategy strategy, Executor exec) {
+		this.waitStrategy = strategy;
+
+		this.wheel = RingBuffer.createSingleProducer(new EventFactory<Set<TimerRegistration>>() {
+			@Override
+			public Set<TimerRegistration> newInstance() {
+				return new ConcurrentSkipListSet<TimerRegistration>();
+			}
+		}, wheelSize);
+
+		this.resolution = res;
+		this.loop = new NamedDaemonThreadFactory(name).newThread(new Runnable() {
+			@Override
+			public void run() {
+				long deadline = System.currentTimeMillis();
+
+				while(true) {
+					Set<TimerRegistration> registrations = wheel.get(wheel.getCursor());
+
+					for(TimerRegistration r : registrations) {
+						if(r.isCancelled()) {
+							registrations.remove(r);
+						} else if(r.ready()) {
+							executor.execute(r);
+							registrations.remove(r);
+
+							if(!r.isCancelAfterUse()) {
+								reschedule(r);
+							}
+						} else if(r.isPaused()) {
+							reschedule(r);
+						} else {
+							r.decrement();
+						}
+					}
+
+					deadline += resolution;
+
+					try {
+						waitStrategy.waitUntil(deadline);
+					} catch(InterruptedException e) {
+						return;
+					}
+
+					wheel.publish(wheel.next());
+				}
+			}
+		});
+
+		this.executor = exec;
+		this.start();
+	}
+
+	@Override
+	public long getResolution() {
+		return resolution;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public TimerRegistration<? extends Consumer<Long>> schedule(Consumer<Long> consumer,
+	                                                            long period,
+	                                                            TimeUnit timeUnit,
+	                                                            long delayInMilliseconds) {
+		Assert.isTrue(!loop.isInterrupted(), "Cannot submit tasks to this timer as it has been cancelled.");
+		return schedule(TimeUnit.MILLISECONDS.convert(period, timeUnit), delayInMilliseconds, consumer);
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public TimerRegistration<? extends Consumer<Long>> submit(Consumer<Long> consumer,
+	                                                          long period,
+	                                                          TimeUnit timeUnit) {
+		Assert.isTrue(!loop.isInterrupted(), "Cannot submit tasks to this timer as it has been cancelled.");
+		long ms = TimeUnit.MILLISECONDS.convert(period, timeUnit);
+		return schedule(ms, ms, consumer).cancelAfterUse();
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public TimerRegistration<? extends Consumer<Long>> submit(Consumer<Long> consumer) {
+		return submit(consumer, resolution, TimeUnit.MILLISECONDS);
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public TimerRegistration<? extends Consumer<Long>> schedule(Consumer<Long> consumer,
+	                                                            long period,
+	                                                            TimeUnit timeUnit) {
+		return schedule(TimeUnit.MILLISECONDS.convert(period, timeUnit), 0, consumer);
+	}
+
+	@SuppressWarnings("unchecked")
+	private TimerRegistration<? extends Consumer<Long>> schedule(long recurringTimeout,
+	                                                             long firstDelay,
+	                                                             Consumer<Long> consumer) {
+		Assert.isTrue(recurringTimeout >= resolution,
+		              "Cannot schedule tasks for amount of time less than timer precision.");
+
+		long offset = recurringTimeout / resolution;
+		long rounds = offset / wheel.getBufferSize();
+
+		long firstFireOffset = firstDelay / resolution;
+		long firstFireRounds = firstFireOffset / wheel.getBufferSize();
+
+		TimerRegistration r = new TimerRegistration(firstFireRounds, offset, consumer, rounds);
+		wheel.get(wheel.getCursor() + firstFireOffset + 1).add(r);
+		return r;
+	}
+
+	/**
+	 * Reschedule a {@link TimerRegistration}  for the next fire
+	 *
+	 * @param registration
+	 */
+	private void reschedule(TimerRegistration registration) {
+		registration.reset();
+		wheel.get(wheel.getCursor() + registration.getOffset()).add(registration);
+	}
+
+	/**
+	 * Start the Timer
+	 */
+	public void start() {
+		this.loop.start();
+		wheel.publish(0);
+	}
+
+	/**
+	 * Cancel current Timer
+	 */
+	public void cancel() {
+		this.loop.interrupt();
+	}
+
+
+	/**
+	 * Timer Registration
+	 *
+	 * @param <T>
+	 * 		type of the Timer Registration Consumer
+	 */
+	public static class TimerRegistration<T extends Consumer<Long>> implements Runnable,
+	                                                                           Comparable,
+	                                                                           Pausable,
+	                                                                           Registration {
+
+		public static int STATUS_PAUSED    = 1;
+		public static int STATUS_CANCELLED = -1;
+		public static int STATUS_READY     = 0;
+
+		private final T             delegate;
+		private final long          rescheduleRounds;
+		private final long          scheduleOffset;
+		private final AtomicLong    rounds;
+		private final AtomicInteger status;
+		private final AtomicBoolean cancelAfterUse;
+
+		/**
+		 * Creates a new Timer Registration with given {@data rounds}, {@data offset} and {@data delegate}.
+		 *
+		 * @param rounds
+		 * 		amount of rounds the Registration should go through until it's elapsed
+		 * @param offset
+		 * 		offset of in the Ring Buffer for rescheduling
+		 * @param delegate
+		 * 		delegate that will be ran whenever the timer is elapsed
+		 */
+		public TimerRegistration(long rounds, long offset, T delegate, long rescheduleRounds) {
+			this.rescheduleRounds = rescheduleRounds;
+			this.scheduleOffset = offset;
+			this.delegate = delegate;
+			this.rounds = new AtomicLong(rounds);
+			this.status = new AtomicInteger(STATUS_READY);
+			this.cancelAfterUse = new AtomicBoolean(false);
+		}
+
+		/**
+		 * Decrement an amount of runs Registration has to run until it's elapsed
+		 */
+		public void decrement() {
+			rounds.decrementAndGet();
+		}
+
+		/**
+		 * Check whether the current Registration is ready for execution
+		 *
+		 * @return whether or not the current Registration is ready for execution
+		 */
+		public boolean ready() {
+			return status.get() == STATUS_READY && rounds.get() == 0;
+		}
+
+		/**
+		 * Run the delegate of the current Registration
+		 */
+		@Override
+		public void run() {
+			delegate.accept(TimeUtils.approxCurrentTimeMillis());
+		}
+
+		/**
+		 * Reset the Registration
+		 */
+		public void reset() {
+			this.status.set(STATUS_READY);
+			this.rounds.set(rescheduleRounds);
+		}
+
+		/**
+		 * Cancel the registration
+		 *
+		 * @return current Registration
+		 */
+		public Registration cancel() {
+			this.status.set(STATUS_CANCELLED);
+			return this;
+		}
+
+		/**
+		 * Check whether the current Registration is cancelled
+		 *
+		 * @return whether or not the current Registration is cancelled
+		 */
+		@Override
+		public boolean isCancelled() {
+			return status.get() == STATUS_CANCELLED;
+		}
+
+		/**
+		 * Pause the current Regisration
+		 *
+		 * @return current Registration
+		 */
+		@Override
+		public Registration pause() {
+			this.status.set(STATUS_PAUSED);
+			return this;
+		}
+
+		/**
+		 * Check whether the current Registration is paused
+		 *
+		 * @return whether or not the current Registration is paused
+		 */
+		@Override
+		public boolean isPaused() {
+			return this.status.get() == STATUS_PAUSED;
+		}
+
+		/**
+		 * Resume current Registration
+		 *
+		 * @return current Registration
+		 */
+		@Override
+		public Registration resume() {
+			this.status.set(STATUS_READY);
+			return this;
+		}
+
+		/**
+		 * Get the offset of the Registration relative to the current Ring Buffer position
+		 * to make it fire timely.
+		 *
+		 * @return the offset of current Registration
+		 */
+		private long getOffset() {
+			return this.scheduleOffset;
+		}
+
+		@Override
+		public Selector getSelector() {
+			return null;
+		}
+
+		@Override
+		public Object getObject() {
+			return null;
+		}
+
+		/**
+		 * Cancel this {@link reactor.timer.HashWheelTimer.TimerRegistration} after it has been selected and used. {@link
+		 * reactor.event.dispatch.Dispatcher} implementations should respect this value and perform
+		 * the cancellation.
+		 *
+		 * @return {@literal this}
+		 */
+		public TimerRegistration<T> cancelAfterUse() {
+			cancelAfterUse.set(false);
+			return this;
+		}
+
+		@Override
+		public boolean isCancelAfterUse() {
+			return this.cancelAfterUse.get();
+		}
+
+		@Override
+		public int compareTo(Object o) {
+			TimerRegistration other = (TimerRegistration)o;
+			if(rounds.get() == other.rounds.get()) {
+				return other == this ? 0 : -1;
+			} else {
+				return Long.compare(rounds.get(), other.rounds.get());
+			}
+		}
+
+		@Override
+		public String toString() {
+			return String.format("HashWheelTimer { Rounds left: %d, Status: %d }", rounds.get(), status.get());
+		}
+	}
+
+	@Override
+	public String toString() {
+		return String.format("HashWheelTimer { Buffer Size: %d, Resolution: %d }",
+		                     wheel.getBufferSize(),
+		                     resolution);
+	}
+
+
+	/**
+	 * Wait strategy for the timer
+	 */
+	public static interface WaitStrategy {
+
+		/**
+		 * Wait until the given deadline, {@data deadlineMilliseconds}
+		 *
+		 * @param deadlineMilliseconds
+		 * 		deadline to wait for, in milliseconds
+		 */
+		public void waitUntil(long deadlineMilliseconds) throws InterruptedException;
+	}
+
+	/**
+	 * Yielding wait strategy.
+	 *
+	 * Spins in the loop, until the deadline is reached. Releases the flow control
+	 * by means of Thread.yield() call. This strategy is less precise than BusySpin
+	 * one, but is more scheduler-friendly.
+	 */
+	public static class YieldingWait implements WaitStrategy {
+
+		@Override
+		public void waitUntil(long deadlineMilliseconds) throws InterruptedException {
+			while(deadlineMilliseconds >= System.currentTimeMillis()) {
+				Thread.yield();
+				if(Thread.currentThread().isInterrupted()) {
+					throw new InterruptedException();
+				}
+			}
+		}
+	}
+
+	/**
+	 * BusySpin wait strategy.
+	 *
+	 * Spins in the loop until the deadline is reached. In a multi-core environment,
+	 * will occupy an entire core. Is more precise than Sleep wait strategy, but
+	 * consumes more resources.
+	 */
+	public static class BusySpinWait implements WaitStrategy {
+
+		@Override
+		public void waitUntil(long deadlineMilliseconds) throws InterruptedException {
+			while(deadlineMilliseconds >= System.currentTimeMillis()) {
+				if(Thread.currentThread().isInterrupted()) {
+					throw new InterruptedException();
+				}
+			}
+		}
+	}
+
+	/**
+	 * Sleep wait strategy.
+	 *
+	 * Will release the flow control, giving other threads a possibility of execution
+	 * on the same processor. Uses less resources than BusySpin wait, but is less
+	 * precise.
+	 */
+	public static class SleepWait implements WaitStrategy {
+
+		@Override
+		public void waitUntil(long deadlineMilliseconds) throws InterruptedException {
+			long sleepTimeMs = deadlineMilliseconds - System.currentTimeMillis();
+			if(sleepTimeMs > 0) {
+				Thread.sleep(sleepTimeMs);
+			}
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/timer/SimpleHashWheelTimer.java b/reactor-core/src/main/java/reactor/timer/SimpleHashWheelTimer.java
new file mode 100644
index 0000000..83211e7
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/timer/SimpleHashWheelTimer.java
@@ -0,0 +1,196 @@
+package reactor.timer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.event.registry.CachingRegistry;
+import reactor.event.registry.Registration;
+import reactor.event.registry.Registry;
+import reactor.event.selector.HeaderResolver;
+import reactor.event.selector.Selector;
+import reactor.function.Consumer;
+import reactor.function.support.CancelConsumerException;
+import reactor.support.NamedDaemonThreadFactory;
+import reactor.util.Assert;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A hashed wheel timer implementation that uses a {@link reactor.event.registry.Registry} and custom {@link
+ * reactor.event.selector.Selector Selectors} to determine when tasks should be executed.
+ * <p>
+ * A {@code SimpleHashWheelTimer} has two variations for scheduling tasks: {@link #schedule(reactor.function.Consumer,
+ * long,
+ * java.util.concurrent.TimeUnit)} and {@link #schedule(reactor.function.Consumer, long, java.util.concurrent.TimeUnit,
+ * long)} which are for scheduling repeating tasks, and {@link #submit(reactor.function.Consumer, long,
+ * java.util.concurrent.TimeUnit)} which is for scheduling single-run delayed tasks.
+ * </p>
+ * <p>
+ * To schedule a repeating task, specify the period of time which should elapse before invoking the given {@link
+ * reactor.function.Consumer}. To schedule a task that repeats every 5 seconds, for example, one would do something
+ * like:
+ * </p>
+ * <p>
+ * <code><pre>
+ *   SimpleHashWheelTimer timer = new SimpleHashWheelTimer();
+ *
+ *   timer.schedule(new Consumer<Long>() {
+ *     public void accept(Long now) {
+ *       // run a task
+ *     }
+ *   }, 5, TimeUnit.SECONDS);
+ * </pre></code>
+ * </p>
+ * <p>
+ * NOTE: Without delaying a task, it will be run immediately, in addition to being run after the elapsed time has
+ * expired. To run a task only once every N time units and not immediately, use the {@link
+ * #schedule(reactor.function.Consumer, long, java.util.concurrent.TimeUnit, long)} method, which allows you to specify
+ * an additional delay that must expire before the task will be executed.
+ * </p>
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class SimpleHashWheelTimer implements Timer {
+
+	private static final Logger LOG = LoggerFactory.getLogger(SimpleHashWheelTimer.class);
+
+	private final Registry<Consumer<Long>> tasks = new CachingRegistry<Consumer<Long>>(true, false, null);
+	private final int    resolution;
+	private final Thread loop;
+
+	/**
+	 * Create a new {@code SimpleHashWheelTimer} using the default resolution of 50ms.
+	 */
+	public SimpleHashWheelTimer() {
+		this(50);
+	}
+
+	/**
+	 * Create a new {@code SimpleHashWheelTimer} using the given timer resolution. All times will rounded up to the
+	 * closest
+	 * multiple of this resolution.
+	 *
+	 * @param resolution
+	 * 		the resolution of this timer, in milliseconds
+	 */
+	public SimpleHashWheelTimer(final int resolution) {
+		this.resolution = resolution;
+
+		this.loop = new NamedDaemonThreadFactory("hash-wheel-timer").newThread(
+				new Runnable() {
+					@Override
+					public void run() {
+						while(!Thread.currentThread().isInterrupted()) {
+							long now = now(resolution);
+							for(Registration<? extends Consumer<Long>> reg : tasks.select(now)) {
+								try {
+									if(reg.isCancelled() || reg.isPaused()) {
+										continue;
+									}
+									reg.getObject().accept(now);
+								} catch(CancelConsumerException cce) {
+									reg.cancel();
+								} catch(Throwable t) {
+									LOG.error(t.getMessage(), t);
+								} finally {
+									if(reg.isCancelAfterUse()) {
+										reg.cancel();
+									}
+								}
+							}
+							try {
+								Thread.sleep(resolution);
+							} catch(InterruptedException e) {
+								Thread.currentThread().interrupt();
+								return;
+							}
+						}
+					}
+				}
+		);
+		this.loop.start();
+	}
+
+	@Override
+	public long getResolution() {
+		return resolution;
+	}
+
+	@Override
+	public Registration<? extends Consumer<Long>> schedule(Consumer<Long> consumer,
+	                                                       long period,
+	                                                       TimeUnit timeUnit,
+	                                                       long delayInMilliseconds) {
+		Assert.isTrue(!loop.isInterrupted(), "Cannot submit tasks to this timer as it has been cancelled.");
+		return tasks.register(
+				new PeriodSelector(TimeUnit.MILLISECONDS.convert(period, timeUnit), delayInMilliseconds, resolution),
+				consumer
+		);
+	}
+
+	@Override
+	public Registration<? extends Consumer<Long>> schedule(Consumer<Long> consumer,
+	                                                       long period,
+	                                                       TimeUnit timeUnit) {
+		return schedule(consumer, period, timeUnit, 0);
+	}
+
+	@Override
+	public Registration<? extends Consumer<Long>> submit(Consumer<Long> consumer,
+	                                                     long delay,
+	                                                     TimeUnit timeUnit) {
+		Assert.isTrue(!loop.isInterrupted(), "Cannot submit tasks to this timer as it has been cancelled.");
+		long ms = TimeUnit.MILLISECONDS.convert(delay, timeUnit);
+		return tasks.register(
+				new PeriodSelector(ms, ms, resolution),
+				consumer
+		).cancelAfterUse();
+	}
+
+	@Override
+	public Registration<? extends Consumer<Long>> submit(Consumer<Long> consumer) {
+		return submit(consumer, resolution, TimeUnit.MILLISECONDS);
+	}
+
+
+	@Override
+	public void cancel() {
+		this.loop.interrupt();
+	}
+
+	private static long now(int resolution) {
+		return (long)(Math.ceil(System.currentTimeMillis() / resolution) * resolution);
+	}
+
+	private static class PeriodSelector implements Selector {
+		private final long period;
+		private final long delay;
+		private final long createdMillis;
+		private final int  resolution;
+
+		private PeriodSelector(long period, long delay, int resolution) {
+			this.period = period;
+			this.delay = delay;
+			this.resolution = resolution;
+			this.createdMillis = now(resolution);
+		}
+
+		@Override
+		public Object getObject() {
+			return period;
+		}
+
+		@Override
+		public boolean matches(Object key) {
+			long now = (Long)key;
+			long period = (long)(Math.ceil((now - createdMillis) / resolution) * resolution);
+			return period >= delay && period % this.period == 0;
+		}
+
+		@Override
+		public HeaderResolver getHeaderResolver() {
+			return null;
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/timer/TimeUtils.java b/reactor-core/src/main/java/reactor/timer/TimeUtils.java
new file mode 100644
index 0000000..2672cc7
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/timer/TimeUtils.java
@@ -0,0 +1,43 @@
+package reactor.timer;
+
+import reactor.function.Consumer;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class TimeUtils {
+
+	private static final int        DEFAULT_RESOLUTION = 100;
+	private static final AtomicLong now                = new AtomicLong();
+	private static Timer timer;
+
+	protected TimeUtils() {
+	}
+
+	public static long approxCurrentTimeMillis() {
+		getTimer();
+		return now.get();
+	}
+
+	public static void setTimer(Timer timer) {
+		timer.schedule(new Consumer<Long>() {
+			@Override
+			public void accept(Long aLong) {
+				now.set(System.currentTimeMillis());
+			}
+		}, DEFAULT_RESOLUTION, TimeUnit.MILLISECONDS, DEFAULT_RESOLUTION);
+		now.set(System.currentTimeMillis());
+		TimeUtils.timer = timer;
+	}
+
+	public static Timer getTimer() {
+		if(null == timer) {
+			setTimer(new SimpleHashWheelTimer(DEFAULT_RESOLUTION));
+		}
+		return timer;
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/timer/Timer.java b/reactor-core/src/main/java/reactor/timer/Timer.java
new file mode 100644
index 0000000..a6d5a0b
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/timer/Timer.java
@@ -0,0 +1,98 @@
+package reactor.timer;
+
+import reactor.event.registry.Registration;
+import reactor.function.Consumer;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Jon Brisbin
+ */
+public interface Timer {
+
+	/**
+	 * Get the resolution of this t{@literal Timer}.
+	 *
+	 * @return the resolution in milliseconds
+	 */
+	long getResolution();
+
+	/**
+	 * Schedule a recurring task. The given {@link reactor.function.Consumer} will be invoked once every N time units
+	 * after the given delay.
+	 *
+	 * @param consumer
+	 * 		the {@code Consumer} to invoke each period
+	 * @param period
+	 * 		the amount of time that should elapse between invocations of the given {@code Consumer}
+	 * @param timeUnit
+	 * 		the unit of time the {@code period} is to be measured in
+	 * @param delayInMilliseconds
+	 * 		a number of milliseconds in which to delay any execution of the given {@code Consumer}
+	 *
+	 * @return a {@link reactor.event.registry.Registration} that can be used to {@link
+	 * reactor.event.registry.Registration#cancel() cancel}, {@link reactor.event.registry.Registration#pause() pause} or
+	 * {@link reactor.event.registry.Registration#resume() resume} the given task.
+	 */
+	Registration<? extends Consumer<Long>> schedule(Consumer<Long> consumer,
+	                                                long period,
+	                                                TimeUnit timeUnit,
+	                                                long delayInMilliseconds);
+
+	/**
+	 * Schedule a recurring task. The given {@link reactor.function.Consumer} will be invoked immediately, as well as
+	 * once
+	 * every N time units.
+	 *
+	 * @param consumer
+	 * 		the {@code Consumer} to invoke each period
+	 * @param period
+	 * 		the amount of time that should elapse between invocations of the given {@code Consumer}
+	 * @param timeUnit
+	 * 		the unit of time the {@code period} is to be measured in
+	 *
+	 * @return a {@link reactor.event.registry.Registration} that can be used to {@link
+	 * reactor.event.registry.Registration#cancel() cancel}, {@link reactor.event.registry.Registration#pause() pause} or
+	 * {@link reactor.event.registry.Registration#resume() resume} the given task.
+	 *
+	 * @see #schedule(reactor.function.Consumer, long, java.util.concurrent.TimeUnit, long)
+	 */
+	Registration<? extends Consumer<Long>> schedule(Consumer<Long> consumer,
+	                                                long period,
+	                                                TimeUnit timeUnit);
+
+	/**
+	 * Submit a task for arbitrary execution after the given time delay.
+	 *
+	 * @param consumer
+	 * 		the {@code Consumer} to invoke
+	 * @param delay
+	 * 		the amount of time that should elapse before invocations of the given {@code Consumer}
+	 * @param timeUnit
+	 * 		the unit of time the {@code period} is to be measured in
+	 *
+	 * @return a {@link reactor.event.registry.Registration} that can be used to {@link
+	 * reactor.event.registry.Registration#cancel() cancel}, {@link reactor.event.registry.Registration#pause() pause} or
+	 * {@link reactor.event.registry.Registration#resume() resume} the given task.
+	 */
+	Registration<? extends Consumer<Long>> submit(Consumer<Long> consumer,
+	                                              long delay,
+	                                              TimeUnit timeUnit);
+
+	/**
+	 * Submit a task for arbitrary execution after the delay of this timer's resolution.
+	 *
+	 * @param consumer
+	 * 		the {@code Consumer} to invoke
+	 *
+	 * @return {@literal this}
+	 */
+	Registration<? extends Consumer<Long>> submit(Consumer<Long> consumer);
+
+	/**
+	 * Cancel this timer by interrupting the task thread. No more tasks can be submitted to this timer after
+	 * cancellation.
+	 */
+	void cancel();
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple.java b/reactor-core/src/main/java/reactor/tuple/Tuple.java
new file mode 100644
index 0000000..34ccaed
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+import reactor.util.ObjectUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * A {@literal Tuple} is an immutable {@link Collection} of objects, each of which can be of an arbitrary type.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+ at SuppressWarnings({"rawtypes"})
+public class Tuple implements Iterable, Serializable {
+
+	private static final long serialVersionUID = 8777121214502020842L;
+
+	protected final Object[] entries;
+	protected final int size;
+
+	/**
+	 * Creates a new {@code Tuple} that holds the given {@code values}.
+	 *
+	 * @param values The values to hold
+	 */
+	public Tuple(@Nonnull Collection<Object> values) {
+		this.entries = (null != values ? values.toArray() : new Object[0]);
+		this.size = entries.length;
+	}
+
+	/**
+	 * Creates a new {@code Tuple} that holds the given {@code values}.
+	 *
+	 * @param values The values to hold
+	 */
+	public Tuple(Object... values) {
+		this.entries = Arrays.copyOf(values, values.length);
+		this.size = values.length;
+	}
+
+	/**
+	 * Create a {@link Tuple1} with the given object.
+	 *
+	 * @param t1   The first value in the tuple.
+	 * @param <T1> The type of the first value.
+	 * @return The new {@link Tuple1}.
+	 */
+	public static <T1> Tuple1<T1> of(T1 t1) {
+		return new Tuple1<T1>(t1);
+	}
+
+	/**
+	 * Create a {@link Tuple2} with the given objects.
+	 *
+	 * @param t1   The first value in the tuple.
+	 * @param t2   The second value in the tuple.
+	 * @param <T1> The type of the first value.
+	 * @param <T2> The type of the second value.
+	 * @return The new {@link Tuple2}.
+	 */
+	public static <T1, T2> Tuple2<T1, T2> of(T1 t1, T2 t2) {
+		return new Tuple2<T1, T2>(t1, t2);
+	}
+
+	/**
+	 * Create a {@link Tuple3} with the given objects.
+	 *
+	 * @param t1   The first value in the tuple.
+	 * @param t2   The second value in the tuple.
+	 * @param t3   The third value in the tuple.
+	 * @param <T1> The type of the first value.
+	 * @param <T2> The type of the second value.
+	 * @param <T3> The type of the third value.
+	 * @return The new {@link Tuple3}.
+	 */
+	public static <T1, T2, T3> Tuple3<T1, T2, T3> of(T1 t1, T2 t2, T3 t3) {
+		return new Tuple3<T1, T2, T3>(t1, t2, t3);
+	}
+
+	/**
+	 * Create a {@link Tuple4} with the given objects.
+	 *
+	 * @param t1   The first value in the tuple.
+	 * @param t2   The second value in the tuple.
+	 * @param t3   The third value in the tuple.
+	 * @param t4   The fourth value in the tuple.
+	 * @param <T1> The type of the first value.
+	 * @param <T2> The type of the second value.
+	 * @param <T3> The type of the third value.
+	 * @param <T4> The type of the fourth value.
+	 * @return The new {@link Tuple4}.
+	 */
+	public static <T1, T2, T3, T4> Tuple4<T1, T2, T3, T4> of(T1 t1, T2 t2, T3 t3, T4 t4) {
+		return new Tuple4<T1, T2, T3, T4>(t1, t2, t3, t4);
+	}
+
+	/**
+	 * Create a {@link Tuple5} with the given objects.
+	 *
+	 * @param t1   The first value in the tuple.
+	 * @param t2   The second value in the tuple.
+	 * @param t3   The third value in the tuple.
+	 * @param t4   The fourth value in the tuple.
+	 * @param t5   The fifth value in the tuple.
+	 * @param <T1> The type of the first value.
+	 * @param <T2> The type of the second value.
+	 * @param <T3> The type of the third value.
+	 * @param <T4> The type of the fourth value.
+	 * @param <T5> The type of the fifth value.
+	 * @return The new {@link Tuple5}.
+	 */
+	public static <T1, T2, T3, T4, T5> Tuple5<T1, T2, T3, T4, T5> of(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) {
+		return new Tuple5<T1, T2, T3, T4, T5>(t1, t2, t3, t4, t5);
+	}
+
+	/**
+	 * Create a {@link Tuple6} with the given objects.
+	 *
+	 * @param t1   The first value in the tuple.
+	 * @param t2   The second value in the tuple.
+	 * @param t3   The third value in the tuple.
+	 * @param t4   The fourth value in the tuple.
+	 * @param t5   The fifth value in the tuple.
+	 * @param t6   The sixth value in the tuple.
+	 * @param <T1> The type of the first value.
+	 * @param <T2> The type of the second value.
+	 * @param <T3> The type of the third value.
+	 * @param <T4> The type of the fourth value.
+	 * @param <T5> The type of the fifth value.
+	 * @param <T6> The type of the sixth value.
+	 * @return The new {@link Tuple6}.
+	 */
+	public static <T1, T2, T3, T4, T5, T6> Tuple6<T1, T2, T3, T4, T5, T6> of(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) {
+		return new Tuple6<T1, T2, T3, T4, T5, T6>(t1, t2, t3, t4, t5, t6);
+	}
+
+	/**
+	 * Create a {@link Tuple7} with the given objects.
+	 *
+	 * @param t1   The first value in the tuple.
+	 * @param t2   The second value in the tuple.
+	 * @param t3   The third value in the tuple.
+	 * @param t4   The fourth value in the tuple.
+	 * @param t5   The fifth value in the tuple.
+	 * @param t6   The sixth value in the tuple.
+	 * @param t7   The seventh value in the tuple.
+	 * @param <T1> The type of the first value.
+	 * @param <T2> The type of the second value.
+	 * @param <T3> The type of the third value.
+	 * @param <T4> The type of the fourth value.
+	 * @param <T5> The type of the fifth value.
+	 * @param <T6> The type of the sixth value.
+	 * @param <T7> The type of the seventh value.
+	 * @return The new {@link Tuple7}.
+	 */
+	public static <T1, T2, T3, T4, T5, T6, T7> Tuple7<T1, T2, T3, T4, T5, T6, T7> of(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) {
+		return new Tuple7<T1, T2, T3, T4, T5, T6, T7>(t1, t2, t3, t4, t5, t6, t7);
+	}
+
+	/**
+	 * Create a {@link Tuple8} with the given objects.
+	 *
+	 * @param t1   The first value in the tuple.
+	 * @param t2   The second value in the tuple.
+	 * @param t3   The third value in the tuple.
+	 * @param t4   The fourth value in the tuple.
+	 * @param t5   The fifth value in the tuple.
+	 * @param t6   The sixth value in the tuple.
+	 * @param t7   The seventh value in the tuple.
+	 * @param t8   The eighth value in the tuple.
+	 * @param <T1> The type of the first value.
+	 * @param <T2> The type of the second value.
+	 * @param <T3> The type of the third value.
+	 * @param <T4> The type of the fourth value.
+	 * @param <T5> The type of the fifth value.
+	 * @param <T6> The type of the sixth value.
+	 * @param <T7> The type of the seventh value.
+	 * @param <T8> The type of the eighth value.
+	 * @return The new {@link Tuple8}.
+	 */
+	public static <T1, T2, T3, T4, T5, T6, T7, T8> Tuple8<T1, T2, T3, T4, T5, T6, T7, T8> of(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) {
+		return new Tuple8<T1, T2, T3, T4, T5, T6, T7, T8>(t1, t2, t3, t4, t5, t6, t7, t8);
+	}
+
+	/**
+	 * Create a {@link TupleN} with the given objects.
+	 *
+	 * @param t1      The first value in the tuple.
+	 * @param t2      The second value in the tuple.
+	 * @param t3      The third value in the tuple.
+	 * @param t4      The fourth value in the tuple.
+	 * @param t5      The fifth value in the tuple.
+	 * @param t6      The sixth value in the tuple.
+	 * @param t7      The seventh value in the tuple.
+	 * @param t8      The eighth value in the tuple.
+	 * @param tRest   The rest of the values.
+	 * @param <T1>    The type of the first value.
+	 * @param <T2>    The type of the second value.
+	 * @param <T3>    The type of the third value.
+	 * @param <T4>    The type of the fourth value.
+	 * @param <T5>    The type of the fifth value.
+	 * @param <T6>    The type of the sixth value.
+	 * @param <T7>    The type of the seventh value.
+	 * @param <TRest> The type of the last tuple.
+	 * @return The new {@link Tuple8}.
+	 */
+	public static <T1, T2, T3, T4, T5, T6, T7, T8, TRest extends Tuple> TupleN<T1, T2, T3, T4, T5, T6, T7, T8, TRest> of(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, Object... tRest) {
+		return new TupleN<T1, T2, T3, T4, T5, T6, T7, T8, TRest>(t1, t2, t3, t4, t5, t6, t7, t8, new Tuple(tRest));
+	}
+
+	/**
+	 * Get the object at the given index.
+	 *
+	 * @param index The index of the object to retrieve. Starts at 0.
+	 * @return The object. Might be {@literal null}.
+	 */
+	@Nullable
+	public Object get(int index) {
+		return (size > 0 && size > index ? entries[index] : null);
+	}
+
+	/**
+	 * Turn this {@literal Tuple} into a plain Object array.
+	 *
+	 * @return A new Object array.
+	 */
+	public Object[] toArray() {
+		return entries;
+	}
+
+	/**
+	 * Return the number of elements in this {@literal Tuple}.
+	 *
+	 * @return The size of this {@literal Tuple}.
+	 */
+	public int size() {
+		return size;
+	}
+
+	@Override
+	@Nonnull
+	public Iterator<?> iterator() {
+		return Arrays.asList(entries).iterator();
+	}
+
+
+  @Override
+  public int hashCode() {
+    if (this.size == 0) {
+      return 0;
+    } else if (this.size == 1) {
+      return ObjectUtils.nullSafeHashCode(this.entries[0]);
+    } else {
+      int hashCode = 1;
+      for (Object entry: this.entries) {
+        hashCode = hashCode ^ ObjectUtils.nullSafeHashCode(entry);
+      }
+      return hashCode;
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o == null) return false;
+
+    if (!(o instanceof Tuple)) return false;
+
+    Tuple cast = (Tuple)o;
+
+    if (this.size != cast.size) return false;
+
+    boolean eq = true;
+
+    for (int i = 0; i < this.size; i++) {
+      if (null != this.entries[i] && !this.entries[i].equals(cast.entries[i])) {
+        return false;
+      }
+    }
+
+    return eq;
+  }
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple1.java b/reactor-core/src/main/java/reactor/tuple/Tuple1.java
new file mode 100644
index 0000000..4633217
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple1.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds a single value
+ *
+ * @param <T1> The type held by this tuple
+ *
+ * @author Jon Brisbin
+ */
+public class Tuple1<T1> extends Tuple {
+
+	private static final long serialVersionUID = -1467756857377152573L;
+
+	Tuple1(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the first object of this {@link Tuple}.
+	 *
+	 * @return The first object, cast to the correct type.
+	 */
+	@SuppressWarnings("unchecked")
+	public T1 getT1() {
+		return (T1) get(0);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple2.java b/reactor-core/src/main/java/reactor/tuple/Tuple2.java
new file mode 100644
index 0000000..909a301
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple2.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds two values
+ *
+ * @param <T1> The type of the first value held by this tuple
+ * @param <T2> The type of the second balue held by this tuple
+ *
+ * @author Jon Brisbin
+ */
+public class Tuple2<T1, T2> extends Tuple1<T1> {
+
+	private static final long serialVersionUID = -565933838909569191L;
+
+	Tuple2(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the second object of this {@link Tuple}.
+	 *
+	 * @return The second object, cast to the correct type.
+	 */
+	@SuppressWarnings("unchecked")
+	public T2 getT2() {
+		return (T2) get(1);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple3.java b/reactor-core/src/main/java/reactor/tuple/Tuple3.java
new file mode 100644
index 0000000..b49e467
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple3.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds three values
+ *
+ * @param <T1> The type of the first value held by this tuple
+ * @param <T2> The type of the second value held by this tuple
+ * @param <T3> The type of the third value held by this tuple
+ *
+ * @author Jon Brisbin
+ */
+public class Tuple3<T1, T2, T3> extends Tuple2<T1, T2> {
+
+	private static final long serialVersionUID = 6315773492205460562L;
+
+	Tuple3(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the third object of this {@link Tuple}.
+	 *
+	 * @return The third object, cast to the correct type.
+	 */
+	@SuppressWarnings("unchecked")
+	public T3 getT3() {
+		return (T3) get(2);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple4.java b/reactor-core/src/main/java/reactor/tuple/Tuple4.java
new file mode 100644
index 0000000..bd7fbbf
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple4.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds four values
+ *
+ * @param <T1> The type of the first value held by this tuple
+ * @param <T2> The type of the second value held by this tuple
+ * @param <T3> The type of the third value held by this tuple
+ * @param <T4> The type of the fourth value held by this tuple
+ *
+ * @author Jon Brisbin
+ */
+public class Tuple4<T1, T2, T3, T4> extends Tuple3<T1, T2, T3> {
+
+	private static final long serialVersionUID = 8075447176142642390L;
+
+	Tuple4(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the fourth object of this {@link Tuple}.
+	 *
+	 * @return The fourth object, cast to the correct type.
+	 */
+	@SuppressWarnings("unchecked")
+	public T4 getT4() {
+		return (T4) get(3);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple5.java b/reactor-core/src/main/java/reactor/tuple/Tuple5.java
new file mode 100644
index 0000000..622f1d3
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple5.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds five values
+ *
+ * @param <T1> The type of the first value held by this tuple
+ * @param <T2> The type of the second value held by this tuple
+ * @param <T3> The type of the third value held by this tuple
+ * @param <T4> The type of the fourth value held by this tuple
+ * @param <T5> The type of the fifth value held by this tuple
+ *
+ * @author Jon Brisbin
+ */
+public class Tuple5<T1, T2, T3, T4, T5> extends Tuple4<T1, T2, T3, T4> {
+
+	private static final long serialVersionUID = -5866370282498275773L;
+
+	Tuple5(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the fifth object of this {@link Tuple}.
+	 *
+	 * @return The fifth object, cast to the correct type.
+	 */
+	@SuppressWarnings("unchecked")
+	public T5 getT5() {
+		return (T5) get(4);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple6.java b/reactor-core/src/main/java/reactor/tuple/Tuple6.java
new file mode 100644
index 0000000..d048c2d
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple6.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds six values
+ *
+ * @param <T1> The type of the first value held by this tuple
+ * @param <T2> The type of the second value held by this tuple
+ * @param <T3> The type of the third value held by this tuple
+ * @param <T4> The type of the fourth value held by this tuple
+ * @param <T5> The type of the fifth value held by this tuple
+ * @param <T6> The type of the sixth value held by this tuple
+ *
+ * @author Jon Brisbin
+ */
+public class Tuple6<T1, T2, T3, T4, T5, T6> extends Tuple5<T1, T2, T3, T4, T5> {
+
+	private static final long serialVersionUID = -4214053259792235250L;
+
+	Tuple6(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the sixth object of this {@link Tuple}.
+	 *
+	 * @return The sixth object, cast to the correct type.
+	 */
+	@SuppressWarnings("unchecked")
+	public T6 getT6() {
+		return (T6) get(5);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple7.java b/reactor-core/src/main/java/reactor/tuple/Tuple7.java
new file mode 100644
index 0000000..3852f1e
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple7.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds seven values
+ *
+ * @param <T1> The type of the first value held by this tuple
+ * @param <T2> The type of the second value held by this tuple
+ * @param <T3> The type of the third value held by this tuple
+ * @param <T4> The type of the fourth value held by this tuple
+ * @param <T5> The type of the fifth value held by this tuple
+ * @param <T6> The type of the sixth value held by this tuple
+ * @param <T7> The type of the seventh value held by this tuple
+ *
+ * @author Jon Brisbin
+ */
+public class Tuple7<T1, T2, T3, T4, T5, T6, T7> extends Tuple6<T1, T2, T3, T4, T5, T6> {
+
+	private static final long serialVersionUID = 8273600047065201704L;
+
+	Tuple7(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the seventh object of this {@link Tuple}.
+	 *
+	 * @return The seventh object, cast to the correct type.
+	 */
+	@SuppressWarnings("unchecked")
+	public T7 getT7() {
+		return (T7) get(6);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/Tuple8.java b/reactor-core/src/main/java/reactor/tuple/Tuple8.java
new file mode 100644
index 0000000..59b54c9
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/Tuple8.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds eight values
+ *
+ * @param <T1> The type of the first value held by this tuple
+ * @param <T2> The type of the second value held by this tuple
+ * @param <T3> The type of the third value held by this tuple
+ * @param <T4> The type of the fourth value held by this tuple
+ * @param <T5> The type of the fifth value held by this tuple
+ * @param <T6> The type of the sixth value held by this tuple
+ * @param <T7> The type of the seventh value held by this tuple
+ * @param <T8> The type of the eighth value held by this tuple
+ *
+ * @author Jon Brisbin
+ */
+public class Tuple8<T1, T2, T3, T4, T5, T6, T7, T8> extends Tuple7<T1, T2, T3, T4, T5, T6, T7> {
+
+	private static final long serialVersionUID = 3070436338779769189L;
+
+	Tuple8(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the eighth object of this {@link Tuple}.
+	 *
+	 * @return The eighth object, cast to the correct type.
+	 */
+	@SuppressWarnings("unchecked")
+	public T8 getT8() {
+		return (T8) get(7);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/TupleN.java b/reactor-core/src/main/java/reactor/tuple/TupleN.java
new file mode 100644
index 0000000..6727bb0
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/TupleN.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+/**
+ * A tuple that holds 9 or more values
+ *
+ * @param <T1> The type of the first value held by this tuple
+ * @param <T2> The type of the second value held by this tuple
+ * @param <T3> The type of the third value held by this tuple
+ * @param <T4> The type of the fourth value held by this tuple
+ * @param <T5> The type of the fifth value held by this tuple
+ * @param <T6> The type of the sixth value held by this tuple
+ * @param <T7> The type of the seventh value held by this tuple
+ * @param <T8> The type of the eighth value held by this tuple
+ * @param <TRest> The type of the tuple that holds the remaining values
+ *
+ * @author Jon Brisbin
+ */
+public class TupleN<T1, T2, T3, T4, T5, T6, T7, T8, TRest extends Tuple> extends Tuple8<T1, T2, T3, T4, T5, T6, T7, T8> {
+
+	private static final long serialVersionUID = 666954435584703227L;
+
+	TupleN(Object... values) {
+		super(values);
+	}
+
+	/**
+	 * Type-safe way to get the remaining objects of this {@link Tuple}.
+	 *
+	 * @return The remaining objects, as a Tuple.
+	 */
+	@SuppressWarnings("unchecked")
+	public TRest getTRest() {
+		return (TRest) get(8);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/tuple/package-info.java b/reactor-core/src/main/java/reactor/tuple/package-info.java
new file mode 100644
index 0000000..c2875e6
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/tuple/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Tuples provide a type-safe way to specify multiple parameters.
+ */
+package reactor.tuple;
\ No newline at end of file
diff --git a/reactor-core/src/main/java/reactor/util/Assert.java b/reactor-core/src/main/java/reactor/util/Assert.java
new file mode 100644
index 0000000..8100ced
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/Assert.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Assertion utility class that assists in validating arguments. Useful for identifying programmer errors early and
+ * clearly at runtime.
+ * <p/>
+ * <p>For example, if the contract of a public method states it does not allow {@code null} arguments, Assert can be
+ * used to validate that contract. Doing this clearly indicates a contract violation when it occurs and protects the
+ * class's invariants.
+ * <p/>
+ * <p>Typically used to validate method arguments rather than configuration properties, to check for cases that are
+ * usually programmer errors rather than configuration errors. In contrast to config initialization code, there is
+ * usally no point in falling back to defaults in such methods.
+ * <p/>
+ * <p>This class is similar to JUnit's assertion library. If an argument value is deemed invalid, an {@link
+ * IllegalArgumentException} is thrown (typically). For example:
+ * <p/>
+ * <pre class="code"> Assert.notNull(clazz, "The class must not be null"); Assert.isTrue(i > 0, "The value must be
+ * greater than zero");</pre>
+ * <p/>
+ * Mainly for internal use within the framework; consider Jakarta's Commons Lang >= 2.0 for a more comprehensive suite
+ * of assertion utilities.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Colin Sampaleanu
+ * @author Rob Harrop
+ * @since 1.1.2
+ */
+ at SuppressWarnings({"rawtypes"})
+public abstract class Assert {
+
+	/**
+	 * Assert a boolean expression, throwing {@code IllegalArgumentException} if the test result is {@code false}. <pre
+	 * class="code">Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
+	 *
+	 * @param expression a boolean expression
+	 * @param message    the exception message to use if the assertion fails
+	 * @throws IllegalArgumentException if expression is {@code false}
+	 */
+	public static void isTrue(boolean expression, String message) {
+		if (!expression) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert a boolean expression, throwing {@code IllegalArgumentException} if the test result is {@code false}. <pre
+	 * class="code">Assert.isTrue(i > 0);</pre>
+	 *
+	 * @param expression a boolean expression
+	 * @throws IllegalArgumentException if expression is {@code false}
+	 */
+	public static void isTrue(boolean expression) {
+		isTrue(expression, "[Assertion failed] - this expression must be true");
+	}
+
+	/**
+	 * Assert that an object is {@code null} . <pre class="code">Assert.isNull(value, "The value must be null");</pre>
+	 *
+	 * @param object  the object to check
+	 * @param message the exception message to use if the assertion fails
+	 * @throws IllegalArgumentException if the object is not {@code null}
+	 */
+	public static void isNull(Object object, String message) {
+		if (object != null) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert that an object is {@code null} . <pre class="code">Assert.isNull(value);</pre>
+	 *
+	 * @param object the object to check
+	 * @throws IllegalArgumentException if the object is not {@code null}
+	 */
+	public static void isNull(Object object) {
+		isNull(object, "[Assertion failed] - the object argument must be null");
+	}
+
+	/**
+	 * Assert that an object is not {@code null} . <pre class="code">Assert.notNull(clazz, "The class must not be
+	 * null");</pre>
+	 *
+	 * @param object  the object to check
+	 * @param message the exception message to use if the assertion fails
+	 * @throws IllegalArgumentException if the object is {@code null}
+	 */
+	public static void notNull(Object object, String message) {
+		if (object == null) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert that an object is not {@code null} . <pre class="code">Assert.notNull(clazz);</pre>
+	 *
+	 * @param object the object to check
+	 * @throws IllegalArgumentException if the object is {@code null}
+	 */
+	public static void notNull(Object object) {
+		notNull(object, "[Assertion failed] - this argument is required; it must not be null");
+	}
+
+	/**
+	 * Assert that the given String is not empty; that is, it must not be {@code null} and not the empty String. <pre
+	 * class="code">Assert.hasLength(name, "Name must not be empty");</pre>
+	 *
+	 * @param text    the String to check
+	 * @param message the exception message to use if the assertion fails
+	 * @see StringUtils#hasLength
+	 */
+	public static void hasLength(String text, String message) {
+		if (!StringUtils.hasLength(text)) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert that the given String is not empty; that is, it must not be {@code null} and not the empty String. <pre
+	 * class="code">Assert.hasLength(name);</pre>
+	 *
+	 * @param text the String to check
+	 * @see StringUtils#hasLength
+	 */
+	public static void hasLength(String text) {
+		hasLength(text,
+							"[Assertion failed] - this String argument must have length; it must not be null or empty");
+	}
+
+	/**
+	 * Assert that the given String has valid text content; that is, it must not be {@code null} and must contain at least
+	 * one non-whitespace character. <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
+	 *
+	 * @param text    the String to check
+	 * @param message the exception message to use if the assertion fails
+	 * @see StringUtils#hasText
+	 */
+	public static void hasText(String text, String message) {
+		if (!StringUtils.hasText(text)) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert that the given String has valid text content; that is, it must not be {@code null} and must contain at least
+	 * one non-whitespace character. <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
+	 *
+	 * @param text the String to check
+	 * @see StringUtils#hasText
+	 */
+	public static void hasText(String text) {
+		hasText(text,
+						"[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
+	}
+
+	/**
+	 * Assert that the given text does not contain the given substring. <pre class="code">Assert.doesNotContain(name,
+	 * "rod", "Name must not contain 'rod'");</pre>
+	 *
+	 * @param textToSearch the text to search
+	 * @param substring    the substring to find within the text
+	 * @param message      the exception message to use if the assertion fails
+	 */
+	public static void doesNotContain(String textToSearch, String substring, String message) {
+		if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) &&
+				textToSearch.contains(substring)) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert that the given text does not contain the given substring. <pre class="code">Assert.doesNotContain(name,
+	 * "rod");</pre>
+	 *
+	 * @param textToSearch the text to search
+	 * @param substring    the substring to find within the text
+	 */
+	public static void doesNotContain(String textToSearch, String substring) {
+		doesNotContain(textToSearch, substring,
+									 "[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
+	}
+
+
+	/**
+	 * Assert that an array has elements; that is, it must not be {@code null} and must have at least one element. <pre
+	 * class="code">Assert.notEmpty(array, "The array must have elements");</pre>
+	 *
+	 * @param array   the array to check
+	 * @param message the exception message to use if the assertion fails
+	 * @throws IllegalArgumentException if the object array is {@code null} or has no elements
+	 */
+	public static void notEmpty(Object[] array, String message) {
+		if (ObjectUtils.isEmpty(array)) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert that an array has elements; that is, it must not be {@code null} and must have at least one element. <pre
+	 * class="code">Assert.notEmpty(array);</pre>
+	 *
+	 * @param array the array to check
+	 * @throws IllegalArgumentException if the object array is {@code null} or has no elements
+	 */
+	public static void notEmpty(Object[] array) {
+		notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
+	}
+
+	/**
+	 * Assert that an array has no null elements. Note: Does not complain if the array is empty! <pre
+	 * class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre>
+	 *
+	 * @param array   the array to check
+	 * @param message the exception message to use if the assertion fails
+	 * @throws IllegalArgumentException if the object array contains a {@code null} element
+	 */
+	public static void noNullElements(Object[] array, String message) {
+		if (array != null) {
+			for (Object element : array) {
+				if (element == null) {
+					throw new IllegalArgumentException(message);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Assert that an array has no null elements. Note: Does not complain if the array is empty! <pre
+	 * class="code">Assert.noNullElements(array);</pre>
+	 *
+	 * @param array the array to check
+	 * @throws IllegalArgumentException if the object array contains a {@code null} element
+	 */
+	public static void noNullElements(Object[] array) {
+		noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
+	}
+
+	/**
+	 * Assert that a collection has elements; that is, it must not be {@code null} and must have at least one element. <pre
+	 * class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
+	 *
+	 * @param collection the collection to check
+	 * @param message    the exception message to use if the assertion fails
+	 * @throws IllegalArgumentException if the collection is {@code null} or has no elements
+	 */
+	public static void notEmpty(Collection collection, String message) {
+		if (CollectionUtils.isEmpty(collection)) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert that a collection has elements; that is, it must not be {@code null} and must have at least one element. <pre
+	 * class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
+	 *
+	 * @param collection the collection to check
+	 * @throws IllegalArgumentException if the collection is {@code null} or has no elements
+	 */
+	public static void notEmpty(Collection collection) {
+		notEmpty(collection,
+						 "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
+	}
+
+	/**
+	 * Assert that a Map has entries; that is, it must not be {@code null} and must have at least one entry. <pre
+	 * class="code">Assert.notEmpty(map, "Map must have entries");</pre>
+	 *
+	 * @param map     the map to check
+	 * @param message the exception message to use if the assertion fails
+	 * @throws IllegalArgumentException if the map is {@code null} or has no entries
+	 */
+	public static void notEmpty(Map map, String message) {
+		if (CollectionUtils.isEmpty(map)) {
+			throw new IllegalArgumentException(message);
+		}
+	}
+
+	/**
+	 * Assert that a Map has entries; that is, it must not be {@code null} and must have at least one entry. <pre
+	 * class="code">Assert.notEmpty(map);</pre>
+	 *
+	 * @param map the map to check
+	 * @throws IllegalArgumentException if the map is {@code null} or has no entries
+	 */
+	public static void notEmpty(Map map) {
+		notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
+	}
+
+
+	/**
+	 * Assert that the provided object is an instance of the provided class. <pre class="code">Assert.instanceOf(Foo.class,
+	 * foo);</pre>
+	 *
+	 * @param clazz the required class
+	 * @param obj   the object to check
+	 * @throws IllegalArgumentException if the object is not an instance of clazz
+	 * @see Class#isInstance
+	 */
+	public static void isInstanceOf(Class<?> clazz, Object obj) {
+		isInstanceOf(clazz, obj, "");
+	}
+
+	/**
+	 * Assert that the provided object is an instance of the provided class. <pre class="code">Assert.instanceOf(Foo.class,
+	 * foo);</pre>
+	 *
+	 * @param type    the type to check against
+	 * @param obj     the object to check
+	 * @param message a message which will be prepended to the message produced by the function itself, and which may be
+	 *                used to provide context. It should normally end in a ": " or ". " so that the function generate
+	 *                message looks ok when prepended to it.
+	 * @throws IllegalArgumentException if the object is not an instance of clazz
+	 * @see Class#isInstance
+	 */
+	public static void isInstanceOf(Class<?> type, Object obj, String message) {
+		notNull(type, "Type to check against must not be null");
+		if (!type.isInstance(obj)) {
+			throw new IllegalArgumentException(
+					(StringUtils.hasLength(message) ? message + " " : "") +
+							"Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
+							"] must be an instance of " + type);
+		}
+	}
+
+	/**
+	 * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. <pre class="code">Assert.isAssignable(Number.class,
+	 * myClass);</pre>
+	 *
+	 * @param superType the super type to check
+	 * @param subType   the sub type to check
+	 * @throws IllegalArgumentException if the classes are not assignable
+	 */
+	public static void isAssignable(Class<?> superType, Class<?> subType) {
+		isAssignable(superType, subType, "");
+	}
+
+	/**
+	 * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. <pre class="code">Assert.isAssignable(Number.class,
+	 * myClass);</pre>
+	 *
+	 * @param superType the super type to check against
+	 * @param subType   the sub type to check
+	 * @param message   a message which will be prepended to the message produced by the function itself, and which may be
+	 *                  used to provide context. It should normally end in a ": " or ". " so that the function generate
+	 *                  message looks ok when prepended to it.
+	 * @throws IllegalArgumentException if the classes are not assignable
+	 */
+	public static void isAssignable(Class<?> superType, Class<?> subType, String message) {
+		notNull(superType, "Type to check against must not be null");
+		if (subType == null || !superType.isAssignableFrom(subType)) {
+			throw new IllegalArgumentException(message + subType + " is not assignable to " + superType);
+		}
+	}
+
+
+	/**
+	 * Assert a boolean expression, throwing {@code IllegalStateException} if the test result is {@code false}. Call isTrue
+	 * if you wish to throw IllegalArgumentException on an assertion failure. <pre class="code">Assert.state(id == null,
+	 * "The id property must not already be initialized");</pre>
+	 *
+	 * @param expression a boolean expression
+	 * @param message    the exception message to use if the assertion fails
+	 * @throws IllegalStateException if expression is {@code false}
+	 */
+	public static void state(boolean expression, String message) {
+		if (!expression) {
+			throw new IllegalStateException(message);
+		}
+	}
+
+	/**
+	 * Assert a boolean expression, throwing {@link IllegalStateException} if the test result is {@code false}. <p>Call
+	 * {@link #isTrue(boolean)} if you wish to throw {@link IllegalArgumentException} on an assertion failure. <pre
+	 * class="code">Assert.state(id == null);</pre>
+	 *
+	 * @param expression a boolean expression
+	 * @throws IllegalStateException if the supplied expression is {@code false}
+	 */
+	public static void state(boolean expression) {
+		state(expression, "[Assertion failed] - this state invariant must be true");
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/util/CollectionUtils.java b/reactor-core/src/main/java/reactor/util/CollectionUtils.java
new file mode 100644
index 0000000..d8a0db2
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/CollectionUtils.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Miscellaneous collection utility methods.
+ * Mainly for internal use within the framework.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Arjen Poutsma
+ * @since 1.1.3
+ */
+ at SuppressWarnings("rawtypes")
+public abstract class CollectionUtils {
+
+	/**
+	 * Return {@code true} if the supplied Collection is {@code null}
+	 * or empty. Otherwise, return {@code false}.
+	 * @param collection the Collection to check
+	 * @return whether the given Collection is empty
+	 */
+	public static boolean isEmpty(Collection collection) {
+		return (collection == null || collection.isEmpty());
+	}
+
+	/**
+	 * Return {@code true} if the supplied Map is {@code null}
+	 * or empty. Otherwise, return {@code false}.
+	 * @param map the Map to check
+	 * @return whether the given Map is empty
+	 */
+	public static boolean isEmpty(Map map) {
+		return (map == null || map.isEmpty());
+	}
+
+	/**
+	 * Convert the supplied array into a List. A primitive array gets
+	 * converted into a List of the appropriate wrapper type.
+	 * <p>A {@code null} source value will be converted to an
+	 * empty List.
+	 * @param source the (potentially primitive) array
+	 * @return the converted List result
+	 * @see ObjectUtils#toObjectArray(Object)
+	 */
+	public static List arrayToList(Object source) {
+		return Arrays.asList(ObjectUtils.toObjectArray(source));
+	}
+
+	/**
+	 * Merge the given array into the given Collection.
+	 * @param array the array to merge (may be {@code null})
+	 * @param collection the target Collection to merge the array into
+	 */
+	@SuppressWarnings("unchecked")
+	public static void mergeArrayIntoCollection(Object array, Collection collection) {
+		if (collection == null) {
+			throw new IllegalArgumentException("Collection must not be null");
+		}
+		Object[] arr = ObjectUtils.toObjectArray(array);
+		for (Object elem : arr) {
+			collection.add(elem);
+		}
+	}
+
+	/**
+	 * Merge the given Properties instance into the given Map,
+	 * copying all properties (key-value pairs) over.
+	 * <p>Uses {@code Properties.propertyNames()} to even catch
+	 * default properties linked into the original Properties instance.
+	 * @param props the Properties instance to merge (may be {@code null})
+	 * @param map the target Map to merge the properties into
+	 */
+	@SuppressWarnings("unchecked")
+	public static void mergePropertiesIntoMap(Properties props, Map map) {
+		if (map == null) {
+			throw new IllegalArgumentException("Map must not be null");
+		}
+		if (props != null) {
+			for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
+				String key = (String) en.nextElement();
+				Object value = props.getProperty(key);
+				if (value == null) {
+					// Potentially a non-String value...
+					value = props.get(key);
+				}
+				map.put(key, value);
+			}
+		}
+	}
+
+
+	/**
+	 * Check whether the given Iterator contains the given element.
+	 * @param iterator the Iterator to check
+	 * @param element the element to look for
+	 * @return {@code true} if found, {@code false} else
+	 */
+	public static boolean contains(Iterator iterator, Object element) {
+		if (iterator != null) {
+			while (iterator.hasNext()) {
+				Object candidate = iterator.next();
+				if (ObjectUtils.nullSafeEquals(candidate, element)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Check whether the given Enumeration contains the given element.
+	 * @param enumeration the Enumeration to check
+	 * @param element the element to look for
+	 * @return {@code true} if found, {@code false} else
+	 */
+	public static boolean contains(Enumeration enumeration, Object element) {
+		if (enumeration != null) {
+			while (enumeration.hasMoreElements()) {
+				Object candidate = enumeration.nextElement();
+				if (ObjectUtils.nullSafeEquals(candidate, element)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Check whether the given Collection contains the given element instance.
+	 * <p>Enforces the given instance to be present, rather than returning
+	 * {@code true} for an equal element as well.
+	 * @param collection the Collection to check
+	 * @param element the element to look for
+	 * @return {@code true} if found, {@code false} else
+	 */
+	public static boolean containsInstance(Collection collection, Object element) {
+		if (collection != null) {
+			for (Object candidate : collection) {
+				if (candidate == element) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Return {@code true} if any element in '{@code candidates}' is
+	 * contained in '{@code source}'; otherwise returns {@code false}.
+	 * @param source the source Collection
+	 * @param candidates the candidates to search for
+	 * @return whether any of the candidates has been found
+	 */
+	public static boolean containsAny(Collection source, Collection candidates) {
+		if (isEmpty(source) || isEmpty(candidates)) {
+			return false;
+		}
+		for (Object candidate : candidates) {
+			if (source.contains(candidate)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Return the first element in '{@code candidates}' that is contained in
+	 * '{@code source}'. If no element in '{@code candidates}' is present in
+	 * '{@code source}' returns {@code null}. Iteration order is
+	 * {@link Collection} implementation specific.
+	 * @param source the source Collection
+	 * @param candidates the candidates to search for
+	 * @return the first present object, or {@code null} if not found
+	 */
+	public static Object findFirstMatch(Collection source, Collection candidates) {
+		if (isEmpty(source) || isEmpty(candidates)) {
+			return null;
+		}
+		for (Object candidate : candidates) {
+			if (source.contains(candidate)) {
+				return candidate;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Find a single value of the given type in the given Collection.
+	 * @param collection the Collection to search
+	 * @param type the type to look for
+	 * @return a value of the given type found if there is a clear match,
+	 * or {@code null} if none or more than one such value found
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> T findValueOfType(Collection<?> collection, Class<T> type) {
+		if (isEmpty(collection)) {
+			return null;
+		}
+		T value = null;
+		for (Object element : collection) {
+			if (type == null || type.isInstance(element)) {
+				if (value != null) {
+					// More than one value found... no clear single value.
+					return null;
+				}
+				value = (T) element;
+			}
+		}
+		return value;
+	}
+
+	/**
+	 * Find a single value of one of the given types in the given Collection:
+	 * searching the Collection for a value of the first type, then
+	 * searching for a value of the second type, etc.
+	 * @param collection the collection to search
+	 * @param types the types to look for, in prioritized order
+	 * @return a value of one of the given types found if there is a clear match,
+	 * or {@code null} if none or more than one such value found
+	 */
+	public static Object findValueOfType(Collection<?> collection, Class<?>[] types) {
+		if (isEmpty(collection) || ObjectUtils.isEmpty(types)) {
+			return null;
+		}
+		for (Class<?> type : types) {
+			Object value = findValueOfType(collection, type);
+			if (value != null) {
+				return value;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Determine whether the given Collection only contains a single unique object.
+	 * @param collection the Collection to check
+	 * @return {@code true} if the collection contains a single reference or
+	 * multiple references to the same instance, {@code false} else
+	 */
+	public static boolean hasUniqueObject(Collection collection) {
+		if (isEmpty(collection)) {
+			return false;
+		}
+		boolean hasCandidate = false;
+		Object candidate = null;
+		for (Object elem : collection) {
+			if (!hasCandidate) {
+				hasCandidate = true;
+				candidate = elem;
+			}
+			else if (candidate != elem) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Find the common element type of the given Collection, if any.
+	 * @param collection the Collection to check
+	 * @return the common element type, or {@code null} if no clear
+	 * common type has been found (or the collection was empty)
+	 */
+	public static Class<?> findCommonElementType(Collection collection) {
+		if (isEmpty(collection)) {
+			return null;
+		}
+		Class<?> candidate = null;
+		for (Object val : collection) {
+			if (val != null) {
+				if (candidate == null) {
+					candidate = val.getClass();
+				}
+				else if (candidate != val.getClass()) {
+					return null;
+				}
+			}
+		}
+		return candidate;
+	}
+
+	/**
+	 * Marshal the elements from the given enumeration into an array of the given type. Enumeration
+	 * elements must be assignable to the type of the given array. The array returned may be a
+	 * different instance than the array given.
+	 *
+	 * @param enumeration The source of the elements
+	 * @param array The array to write the elements into
+	 * @param <A> The type of array elements
+	 * @param <E> The type of the source elements
+	 *
+	 * @return An array containing the elements in the enumeration
+	 *
+	 * @see List#toArray(Object[])
+	 */
+	public static <A,E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) {
+		ArrayList<A> elements = new ArrayList<A>();
+		while (enumeration.hasMoreElements()) {
+			elements.add(enumeration.nextElement());
+		}
+		return elements.toArray(array);
+	}
+
+	/**
+	 * Adapt an enumeration to an iterator.
+	 * @param enumeration the enumeration
+	 * @return the iterator
+	 */
+	public static <E> Iterator<E> toIterator(Enumeration<E> enumeration) {
+		return new EnumerationIterator<E>(enumeration);
+	}
+
+	/**
+	 * Adapts a {@code Map<K, List<V>>} to an {@code MultiValueMap<K,V>}.
+	 *
+	 * @param map the map
+	 * @return the multi-value map
+	 */
+	public static <K, V> MultiValueMap<K, V> toMultiValueMap(Map<K, List<V>> map) {
+		return new MultiValueMapAdapter<K, V>(map);
+
+	}
+
+	/**
+	 * Returns an unmodifiable view of the specified multi-value map.
+	 *
+	 * @param  map the map for which an unmodifiable view is to be returned.
+	 * @return an unmodifiable view of the specified multi-value map.
+	 */
+	public static <K,V> MultiValueMap<K,V> unmodifiableMultiValueMap(MultiValueMap<? extends K, ? extends V> map) {
+		Assert.notNull(map, "'map' must not be null");
+		Map<K, List<V>> result = new LinkedHashMap<K, List<V>>(map.size());
+		for (Map.Entry<? extends K, ? extends List<? extends V>> entry : map.entrySet()) {
+			List<V> values = Collections.unmodifiableList(entry.getValue());
+			result.put(entry.getKey(), values);
+		}
+		Map<K, List<V>> unmodifiableMap = Collections.unmodifiableMap(result);
+		return toMultiValueMap(unmodifiableMap);
+	}
+
+
+
+	/**
+	 * Iterator wrapping an Enumeration.
+	 */
+	private static class EnumerationIterator<E> implements Iterator<E> {
+
+		private Enumeration<E> enumeration;
+
+		private EnumerationIterator(Enumeration<E> enumeration) {
+			this.enumeration = enumeration;
+		}
+
+		@Override
+		public boolean hasNext() {
+			return this.enumeration.hasMoreElements();
+		}
+
+		@Override
+		public E next() {
+			return this.enumeration.nextElement();
+		}
+
+		@Override
+		public void remove() throws UnsupportedOperationException {
+			throw new UnsupportedOperationException("Not supported");
+		}
+	}
+
+	/**
+	 * Adapts a Map to the MultiValueMap contract.
+	 */
+	@SuppressWarnings("serial")
+	private static class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializable {
+
+		private final Map<K, List<V>> map;
+
+		private MultiValueMapAdapter(Map<K, List<V>> map) {
+			Assert.notNull(map, "'map' must not be null");
+			this.map = map;
+		}
+
+		@Override
+		public void add(K key, V value) {
+			List<V> values = this.map.get(key);
+			if (values == null) {
+				values = new LinkedList<V>();
+				this.map.put(key, values);
+			}
+			values.add(value);
+		}
+
+		@Override
+		public V getFirst(K key) {
+			List<V> values = this.map.get(key);
+			return (values != null ? values.get(0) : null);
+		}
+
+		@Override
+		public void set(K key, V value) {
+			List<V> values = new LinkedList<V>();
+			values.add(value);
+			this.map.put(key, values);
+		}
+
+		@Override
+		public void setAll(Map<K, V> values) {
+			for (Entry<K, V> entry : values.entrySet()) {
+				set(entry.getKey(), entry.getValue());
+			}
+		}
+
+		@Override
+		public Map<K, V> toSingleValueMap() {
+			LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<K,V>(this.map.size());
+			for (Entry<K, List<V>> entry : map.entrySet()) {
+				singleValueMap.put(entry.getKey(), entry.getValue().get(0));
+			}
+			return singleValueMap;
+		}
+
+		@Override
+		public int size() {
+			return this.map.size();
+		}
+
+		@Override
+		public boolean isEmpty() {
+			return this.map.isEmpty();
+		}
+
+		@Override
+		public boolean containsKey(Object key) {
+			return this.map.containsKey(key);
+		}
+
+		@Override
+		public boolean containsValue(Object value) {
+			return this.map.containsValue(value);
+		}
+
+		@Override
+		public List<V> get(Object key) {
+			return this.map.get(key);
+		}
+
+		@Override
+		public List<V> put(K key, List<V> value) {
+			return this.map.put(key, value);
+		}
+
+		@Override
+		public List<V> remove(Object key) {
+			return this.map.remove(key);
+		}
+
+		@Override
+		public void putAll(Map<? extends K, ? extends List<V>> m) {
+			this.map.putAll(m);
+		}
+
+		@Override
+		public void clear() {
+			this.map.clear();
+		}
+
+		@Override
+		public Set<K> keySet() {
+			return this.map.keySet();
+		}
+
+		@Override
+		public Collection<List<V>> values() {
+			return this.map.values();
+		}
+
+		@Override
+		public Set<Entry<K, List<V>>> entrySet() {
+			return this.map.entrySet();
+		}
+
+		@Override
+		public boolean equals(Object other) {
+			if (this == other) {
+				return true;
+			}
+			return map.equals(other);
+		}
+
+		@Override
+		public int hashCode() {
+			return this.map.hashCode();
+		}
+
+		@Override
+		public String toString() {
+			return this.map.toString();
+		}
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/util/IoUtils.java b/reactor-core/src/main/java/reactor/util/IoUtils.java
new file mode 100644
index 0000000..2610818
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/IoUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * IO-related utility methods
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public final class IoUtils {
+
+	private IoUtils() {
+
+	}
+
+	/**
+	 * {@link Closeable#close Closes} each of the {@code closeables} swallowing any
+	 * {@link IOException IOExceptions} that are thrown.
+	 *
+	 * @param closeables to be closed
+	 */
+	public static void closeQuietly(Closeable... closeables) {
+		for (Closeable closeable: closeables) {
+			if (closeable != null) {
+				try {
+					closeable.close();
+				} catch (IOException ioe) {
+					// Closing quietly.
+				}
+			}
+		}
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/util/LinkedCaseInsensitiveMap.java b/reactor-core/src/main/java/reactor/util/LinkedCaseInsensitiveMap.java
new file mode 100644
index 0000000..aff9ca6
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/LinkedCaseInsensitiveMap.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * {@link LinkedHashMap} variant that stores String keys in a case-insensitive manner, for
+ * example for key-based access in a results table.
+ *
+ * <p>Preserves the original order as well as the original casing of keys, while allowing
+ * for contains, get and remove calls with any case of key.
+ *
+ * <p>Does <i>not</i> support {@code null} keys.
+ *
+ * @param <V> The type of the values in the map
+ *
+ * @author Juergen Hoeller
+ */
+ at SuppressWarnings("serial")
+public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
+
+	private final Map<String, String> caseInsensitiveKeys;
+
+	private final Locale locale;
+
+
+	/**
+	 * Create a new LinkedCaseInsensitiveMap for the default Locale.
+	 * @see java.lang.String#toLowerCase()
+	 */
+	public LinkedCaseInsensitiveMap() {
+		this(null);
+	}
+
+	/**
+	 * Create a new LinkedCaseInsensitiveMap that stores lower-case keys
+	 * according to the given Locale.
+	 * @param locale the Locale to use for lower-case conversion
+	 * @see java.lang.String#toLowerCase(java.util.Locale)
+	 */
+	public LinkedCaseInsensitiveMap(Locale locale) {
+		super();
+		this.caseInsensitiveKeys = new HashMap<String, String>();
+		this.locale = (locale != null ? locale : Locale.getDefault());
+	}
+
+	/**
+	 * Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap}
+	 * with the given initial capacity and stores lower-case keys according
+	 * to the default Locale.
+	 * @param initialCapacity the initial capacity
+	 * @see java.lang.String#toLowerCase()
+	 */
+	public LinkedCaseInsensitiveMap(int initialCapacity) {
+		this(initialCapacity, null);
+	}
+
+	/**
+	 * Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap}
+	 * with the given initial capacity and stores lower-case keys according
+	 * to the given Locale.
+	 * @param initialCapacity the initial capacity
+	 * @param locale the Locale to use for lower-case conversion
+	 * @see java.lang.String#toLowerCase(java.util.Locale)
+	 */
+	public LinkedCaseInsensitiveMap(int initialCapacity, Locale locale) {
+		super(initialCapacity);
+		this.caseInsensitiveKeys = new HashMap<String, String>(initialCapacity);
+		this.locale = (locale != null ? locale : Locale.getDefault());
+	}
+
+
+	@Override
+	public V put(String key, V value) {
+		String oldKey = this.caseInsensitiveKeys.put(convertKey(key), key);
+		if (oldKey != null && !oldKey.equals(key)) {
+			super.remove(oldKey);
+		}
+		return super.put(key, value);
+	}
+
+	@Override
+	public void putAll(Map<? extends String, ? extends V> map) {
+		if (map.isEmpty()) {
+			return;
+		}
+		for (Map.Entry<? extends String, ? extends V> entry : map.entrySet()) {
+			put(entry.getKey(), entry.getValue());
+		}
+	}
+
+	@Override
+	public boolean containsKey(Object key) {
+		return (key instanceof String && this.caseInsensitiveKeys.containsKey(convertKey((String) key)));
+	}
+
+	@Override
+	public V get(Object key) {
+		if (key instanceof String) {
+			return super.get(this.caseInsensitiveKeys.get(convertKey((String) key)));
+		}
+		else {
+			return null;
+		}
+	}
+
+	@Override
+	public V remove(Object key) {
+		if (key instanceof String ) {
+			return super.remove(this.caseInsensitiveKeys.remove(convertKey((String) key)));
+		}
+		else {
+			return null;
+		}
+	}
+
+	@Override
+	public void clear() {
+		this.caseInsensitiveKeys.clear();
+		super.clear();
+	}
+
+
+	/**
+	 * Convert the given key to a case-insensitive key.
+	 * <p>The default implementation converts the key
+	 * to lower-case according to this Map's Locale.
+	 * @param key the user-specified key
+	 * @return the key to use for storing
+	 * @see java.lang.String#toLowerCase(java.util.Locale)
+	 */
+	protected String convertKey(String key) {
+		return key.toLowerCase(this.locale);
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/util/LinkedMultiValueMap.java b/reactor-core/src/main/java/reactor/util/LinkedMultiValueMap.java
new file mode 100644
index 0000000..4677f68
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/LinkedMultiValueMap.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Simple implementation of {@link MultiValueMap} that wraps a {@link LinkedHashMap}, storing
+ * multiple values in a {@link LinkedList}.
+ *
+ * <p>This Map implementation is generally not thread-safe. It is primarily designed for data
+ * structures exposed from request objects, for use in a single thread only.
+ *
+ * @param <K> The type of the map's keys
+ * @param <V> The type of the map's values
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ */
+public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializable {
+
+	private static final long serialVersionUID = 3801124242820219131L;
+
+	private final Map<K, List<V>> targetMap;
+
+
+	/**
+	 * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}.
+	 */
+	public LinkedMultiValueMap() {
+		this.targetMap = new LinkedHashMap<K, List<V>>();
+	}
+
+	/**
+	 * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}
+	 * with the given initial capacity.
+	 * @param initialCapacity the initial capacity
+	 */
+	public LinkedMultiValueMap(int initialCapacity) {
+		this.targetMap = new LinkedHashMap<K, List<V>>(initialCapacity);
+	}
+
+	/**
+	 * Copy constructor: Create a new LinkedMultiValueMap with the same mappings
+	 * as the specified Map.
+	 * @param otherMap the Map whose mappings are to be placed in this Map
+	 */
+	public LinkedMultiValueMap(Map<K, List<V>> otherMap) {
+		this.targetMap = new LinkedHashMap<K, List<V>>(otherMap);
+	}
+
+
+	// MultiValueMap implementation
+
+	@Override
+	public void add(K key, V value) {
+		List<V> values = this.targetMap.get(key);
+		if (values == null) {
+			values = new LinkedList<V>();
+			this.targetMap.put(key, values);
+		}
+		values.add(value);
+	}
+
+	@Override
+	public V getFirst(K key) {
+		List<V> values = this.targetMap.get(key);
+		return (values != null ? values.get(0) : null);
+	}
+
+	@Override
+	public void set(K key, V value) {
+		List<V> values = new LinkedList<V>();
+		values.add(value);
+		this.targetMap.put(key, values);
+	}
+
+	@Override
+	public void setAll(Map<K, V> values) {
+		for (Entry<K, V> entry : values.entrySet()) {
+			set(entry.getKey(), entry.getValue());
+		}
+	}
+
+	@Override
+	public Map<K, V> toSingleValueMap() {
+		LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<K,V>(this.targetMap.size());
+		for (Entry<K, List<V>> entry : targetMap.entrySet()) {
+			singleValueMap.put(entry.getKey(), entry.getValue().get(0));
+		}
+		return singleValueMap;
+	}
+
+
+	// Map implementation
+
+	@Override
+	public int size() {
+		return this.targetMap.size();
+	}
+
+	@Override
+	public boolean isEmpty() {
+		return this.targetMap.isEmpty();
+	}
+
+	@Override
+	public boolean containsKey(Object key) {
+		return this.targetMap.containsKey(key);
+	}
+
+	@Override
+	public boolean containsValue(Object value) {
+		return this.targetMap.containsValue(value);
+	}
+
+	@Override
+	public List<V> get(Object key) {
+		return this.targetMap.get(key);
+	}
+
+	@Override
+	public List<V> put(K key, List<V> value) {
+		return this.targetMap.put(key, value);
+	}
+
+	@Override
+	public List<V> remove(Object key) {
+		return this.targetMap.remove(key);
+	}
+
+	@Override
+	public void putAll(Map<? extends K, ? extends List<V>> m) {
+		this.targetMap.putAll(m);
+	}
+
+	@Override
+	public void clear() {
+		this.targetMap.clear();
+	}
+
+	@Override
+	public Set<K> keySet() {
+		return this.targetMap.keySet();
+	}
+
+	@Override
+	public Collection<List<V>> values() {
+		return this.targetMap.values();
+	}
+
+	@Override
+	public Set<Entry<K, List<V>>> entrySet() {
+		return this.targetMap.entrySet();
+	}
+
+
+	@Override
+	public boolean equals(Object obj) {
+		return this.targetMap.equals(obj);
+	}
+
+	@Override
+	public int hashCode() {
+		return this.targetMap.hashCode();
+	}
+
+	@Override
+	public String toString() {
+		return this.targetMap.toString();
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/util/MultiValueMap.java b/reactor-core/src/main/java/reactor/util/MultiValueMap.java
new file mode 100644
index 0000000..e93bb06
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/MultiValueMap.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Extension of the {@code Map} interface that stores multiple values.
+ *
+ * @param <K> The type of the map's keys
+ * @param <V> The type of the map's values
+ *
+ * @author Arjen Poutsma
+ */
+public interface MultiValueMap<K, V> extends Map<K, List<V>> {
+
+	/**
+	 * Return the first value for the given key.
+	 * @param key the key
+	 * @return the first value for the specified key, or {@code null}
+	 */
+	V getFirst(K key);
+
+	/**
+	 * Add the given single value to the current list of values for the given key.
+	 * @param key the key
+	 * @param value the value to be added
+	 */
+	void add(K key, V value);
+
+	/**
+	 * Set the given single value under the given key.
+	 * @param key the key
+	 * @param value the value to set
+	 */
+	void set(K key, V value);
+
+	/**
+	 * Set the given values under.
+	 * @param values the values.
+	 */
+	void setAll(Map<K, V> values);
+
+	/**
+	 * Returns the first values contained in this {@code MultiValueMap}.
+	 * @return a single value representation of this map
+	 */
+	Map<K, V> toSingleValueMap();
+
+}
diff --git a/reactor-core/src/main/java/reactor/util/ObjectUtils.java b/reactor-core/src/main/java/reactor/util/ObjectUtils.java
new file mode 100644
index 0000000..c31996f
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/ObjectUtils.java
@@ -0,0 +1,995 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ *
+ * <p>Mainly for internal use within the framework; consider
+ * <a href="http://jakarta.apache.org/commons/lang/">Jakarta's Commons Lang</a> for a more
+ * comprehensive suite of object utilities.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ *
+ */
+public abstract class ObjectUtils {
+
+	private static final int INITIAL_HASH = 7;
+	private static final int MULTIPLIER = 31;
+
+	private static final String EMPTY_STRING = "";
+	private static final String NULL_STRING = "null";
+	private static final String ARRAY_START = "{";
+	private static final String ARRAY_END = "}";
+	private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;
+	private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
+
+
+	/**
+	 * Return whether the given throwable is a checked exception: that is, neither a
+	 * RuntimeException nor an Error.
+	 *
+	 * @param ex the throwable to check
+	 *
+	 * @return whether the throwable is a checked exception
+	 *
+	 * @see java.lang.Exception
+	 * @see java.lang.RuntimeException
+	 * @see java.lang.Error
+	 */
+	public static boolean isCheckedException(Throwable ex) {
+		return !(ex instanceof RuntimeException || ex instanceof Error);
+	}
+
+	/**
+	 * Check whether the given exception is compatible with the exceptions declared in a
+	 * throws clause.
+	 *
+	 * @param ex the exception to checked
+	 * @param declaredExceptions the exceptions declared in the throws clause
+	 *
+	 * @return whether the given exception is compatible
+	 */
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	public static boolean isCompatibleWithThrowsClause(Throwable ex, Class[] declaredExceptions) {
+		if (!isCheckedException(ex)) {
+			return true;
+		}
+		if (declaredExceptions != null) {
+			int i = 0;
+			while (i < declaredExceptions.length) {
+				if (declaredExceptions[i].isAssignableFrom(ex.getClass())) {
+					return true;
+				}
+				i++;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Determine whether the given object is an array: either an Object array or a primitive
+	 * array.
+	 *
+	 * @param obj the object to check
+	 *
+	 * @return {@code true} if the object is an array, {@code false} otherwise
+	 */
+	public static boolean isArray(Object obj) {
+		return (obj != null && obj.getClass().isArray());
+	}
+
+	/**
+	 * Determine whether the given array is empty: i.e. {@code null} or of zero length.
+	 *
+	 * @param array the array to check
+	 *
+	 * @return {@code true} if the array is empty or {@code null}, {@code false} otherwise
+	 */
+	public static boolean isEmpty(Object[] array) {
+		return (array == null || array.length == 0);
+	}
+
+	/**
+	 * Check whether the given array contains the given element.
+	 *
+	 * @param array the array to check (may be {@code null}, in which case the return value
+	 *        will always be {@code false})
+	 * @param element the element to check for
+	 *
+	 * @return whether the element has been found in the given array
+	 */
+	public static boolean containsElement(Object[] array, Object element) {
+		if (array == null) {
+			return false;
+		}
+		for (Object arrayEle : array) {
+			if (nullSafeEquals(arrayEle, element)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Check whether the given array of enum constants contains a constant with the given name,
+	 * ignoring case when determining a match.
+	 *
+	 * @param enumValues the enum values to check, typically the product of a call to
+	 *        MyEnum.values()
+	 * @param constant the constant name to find (must not be null or empty string)
+	 *
+	 * @return whether the constant has been found in the given array
+	 */
+	public static boolean containsConstant(Enum<?>[] enumValues, String constant) {
+		return containsConstant(enumValues, constant, false);
+	}
+
+	/**
+	 * Check whether the given array of enum constants contains a constant with the given name.
+	 *
+	 * @param enumValues the enum values to check, typically the product of a call to
+	 *        MyEnum.values()
+	 * @param constant the constant name to find (must not be null or empty string)
+	 * @param caseSensitive whether case is significant in determining a match
+	 *
+	 * @return whether the constant has been found in the given array
+	 */
+	public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) {
+		for (Enum<?> candidate : enumValues) {
+			if (caseSensitive ?
+					candidate.toString().equals(constant) :
+					candidate.toString().equalsIgnoreCase(constant)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Case insensitive alternative to {@link Enum#valueOf(Class, String)}.
+	 *
+	 * @param <E> the concrete Enum type
+	 * @param enumValues the array of all Enum constants in question, usually per Enum.values()
+	 * @param constant the constant to get the enum value of
+	 *
+	 * @return The matching enum
+	 *
+	 * @throws IllegalArgumentException if the given constant is not found in the given array
+	 *         of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to avoid
+	 *         this exception.
+	 */
+	public static <E extends Enum<?>> E caseInsensitiveValueOf(E[] enumValues, String constant) {
+		for (E candidate : enumValues) {
+			if(candidate.toString().equalsIgnoreCase(constant)) {
+				return candidate;
+			}
+		}
+		throw new IllegalArgumentException(
+				String.format("constant [%s] does not exist in enum type %s",
+						constant, enumValues.getClass().getComponentType().getName()));
+	}
+
+	/**
+	 * Append the given object to the given array, returning a new array consisting of the input
+	 * array contents plus the given object.
+	 *
+	 * @param array the array to append to (can be {@code null})
+	 * @param obj the object to append
+	 *
+	 * @return the new array (of the same component type; never {@code null})
+	 */
+	public static <A,O extends A> A[] addObjectToArray(A[] array, O obj) {
+		Class<?> compType = Object.class;
+		if (array != null) {
+			compType = array.getClass().getComponentType();
+		}
+		else if (obj != null) {
+			compType = obj.getClass();
+		}
+		int newArrLength = (array != null ? array.length + 1 : 1);
+		@SuppressWarnings("unchecked")
+		A[] newArr = (A[]) Array.newInstance(compType, newArrLength);
+		if (array != null) {
+			System.arraycopy(array, 0, newArr, 0, array.length);
+		}
+		newArr[newArr.length - 1] = obj;
+		return newArr;
+	}
+
+	/**
+	 * Convert the given array (which may be a primitive array) to an object array (if necessary
+	 * of primitive wrapper objects).
+	 * <p>A {@code null} source value will be converted to an empty Object array.
+	 *
+	 * @param source the (potentially primitive) array
+	 *
+	 * @return the corresponding object array (never {@code null})
+	 *
+	 * @throws IllegalArgumentException if the parameter is not an array
+	 */
+	@SuppressWarnings("rawtypes")
+	public static Object[] toObjectArray(Object source) {
+		if (source instanceof Object[]) {
+			return (Object[]) source;
+		}
+		if (source == null) {
+			return new Object[0];
+		}
+		if (!source.getClass().isArray()) {
+			throw new IllegalArgumentException("Source is not an array: " + source);
+		}
+		int length = Array.getLength(source);
+		if (length == 0) {
+			return new Object[0];
+		}
+		Class wrapperType = Array.get(source, 0).getClass();
+		Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
+		for (int i = 0; i < length; i++) {
+			newArray[i] = Array.get(source, i);
+		}
+		return newArray;
+	}
+
+
+	//---------------------------------------------------------------------
+	// Convenience methods for content-based equality/hash-code handling
+	//---------------------------------------------------------------------
+
+	/**
+	 * Determine if the given objects are equal, returning {@code true} if both are {@code
+	 * null} or {@code false} if only one is {@code null}.
+	 * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+	 * check based on the array elements rather than the array reference.
+	 *
+	 * @param o1 first Object to compare
+	 * @param o2 second Object to compare
+	 *
+	 * @return whether the given objects are equal
+	 *
+	 * @see java.util.Arrays#equals
+	 */
+	public static boolean nullSafeEquals(Object o1, Object o2) {
+		if (o1 == o2) {
+			return true;
+		}
+		if (o1 == null || o2 == null) {
+			return false;
+		}
+		if (o1.equals(o2)) {
+			return true;
+		}
+		if (o1.getClass().isArray() && o2.getClass().isArray()) {
+			if (o1 instanceof Object[] && o2 instanceof Object[]) {
+				return Arrays.equals((Object[]) o1, (Object[]) o2);
+			}
+			if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+				return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+			}
+			if (o1 instanceof byte[] && o2 instanceof byte[]) {
+				return Arrays.equals((byte[]) o1, (byte[]) o2);
+			}
+			if (o1 instanceof char[] && o2 instanceof char[]) {
+				return Arrays.equals((char[]) o1, (char[]) o2);
+			}
+			if (o1 instanceof double[] && o2 instanceof double[]) {
+				return Arrays.equals((double[]) o1, (double[]) o2);
+			}
+			if (o1 instanceof float[] && o2 instanceof float[]) {
+				return Arrays.equals((float[]) o1, (float[]) o2);
+			}
+			if (o1 instanceof int[] && o2 instanceof int[]) {
+				return Arrays.equals((int[]) o1, (int[]) o2);
+			}
+			if (o1 instanceof long[] && o2 instanceof long[]) {
+				return Arrays.equals((long[]) o1, (long[]) o2);
+			}
+			if (o1 instanceof short[] && o2 instanceof short[]) {
+				return Arrays.equals((short[]) o1, (short[]) o2);
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Return a hash code for the given object; typically the value of {@link
+	 * Object#hashCode()}. If the object is an array, this method will delegate to any of the
+	 * {@code nullSafeHashCode} methods for arrays in this class. If the object is {@code null},
+	 * this method returns 0.
+	 *
+	 * @param obj the object
+	 *
+	 * @return a hash code for the given object
+	 *
+	 * @see #nullSafeHashCode(Object[])
+	 * @see #nullSafeHashCode(boolean[])
+	 * @see #nullSafeHashCode(byte[])
+	 * @see #nullSafeHashCode(char[])
+	 * @see #nullSafeHashCode(double[])
+	 * @see #nullSafeHashCode(float[])
+	 * @see #nullSafeHashCode(int[])
+	 * @see #nullSafeHashCode(long[])
+	 * @see #nullSafeHashCode(short[])
+	 */
+	public static int nullSafeHashCode(Object obj) {
+		if (obj == null) {
+			return 0;
+		}
+		if (obj.getClass().isArray()) {
+			if (obj instanceof Object[]) {
+				return nullSafeHashCode((Object[]) obj);
+			}
+			if (obj instanceof boolean[]) {
+				return nullSafeHashCode((boolean[]) obj);
+			}
+			if (obj instanceof byte[]) {
+				return nullSafeHashCode((byte[]) obj);
+			}
+			if (obj instanceof char[]) {
+				return nullSafeHashCode((char[]) obj);
+			}
+			if (obj instanceof double[]) {
+				return nullSafeHashCode((double[]) obj);
+			}
+			if (obj instanceof float[]) {
+				return nullSafeHashCode((float[]) obj);
+			}
+			if (obj instanceof int[]) {
+				return nullSafeHashCode((int[]) obj);
+			}
+			if (obj instanceof long[]) {
+				return nullSafeHashCode((long[]) obj);
+			}
+			if (obj instanceof short[]) {
+				return nullSafeHashCode((short[]) obj);
+			}
+		}
+		return obj.hashCode();
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(Object[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + nullSafeHashCode(array[i]);
+		}
+		return hash;
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(boolean[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + hashCode(array[i]);
+		}
+		return hash;
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(byte[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + array[i];
+		}
+		return hash;
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(char[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + array[i];
+		}
+		return hash;
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(double[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + hashCode(array[i]);
+		}
+		return hash;
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(float[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + hashCode(array[i]);
+		}
+		return hash;
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(int[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + array[i];
+		}
+		return hash;
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(long[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + hashCode(array[i]);
+		}
+		return hash;
+	}
+
+	/**
+	 * Return a hash code based on the contents of the specified array. If {@code array} is {@code
+	 * null}, this method returns 0.
+	 *
+	 * @param array the array
+	 *
+	 * @return the hash code for the array
+	 */
+	public static int nullSafeHashCode(short[] array) {
+		if (array == null) {
+			return 0;
+		}
+		int hash = INITIAL_HASH;
+		int arraySize = array.length;
+		for (int i = 0; i < arraySize; i++) {
+			hash = MULTIPLIER * hash + array[i];
+		}
+		return hash;
+	}
+
+	/**
+	 * Return the same value as {@link Boolean#hashCode()}}.
+	 *
+	 * @param bool the boolean to hash
+	 *
+	 * @return a hash code for the boolean
+	 *
+	 * @see Boolean#hashCode()
+	 */
+	public static int hashCode(boolean bool) {
+		return bool ? 1231 : 1237;
+	}
+
+	/**
+	 * Return the same value as {@link Double#hashCode()}}.
+	 *
+	 * @param dbl the double to hash
+	 *
+	 * @return a hash code for the double
+	 *
+	 * @see Double#hashCode()
+	 */
+	public static int hashCode(double dbl) {
+		long bits = Double.doubleToLongBits(dbl);
+		return hashCode(bits);
+	}
+
+	/**
+	 * Return the same value as {@link Float#hashCode()}}.
+	 *
+	 * @param flt the float to hash
+	 *
+	 * @return a hash code for the float
+	 *
+	 * @see Float#hashCode()
+	 */
+	public static int hashCode(float flt) {
+		return Float.floatToIntBits(flt);
+	}
+
+	/**
+	 * Return the same value as {@link Long#hashCode()}}.
+	 *
+	 * @param lng the long to hash
+	 *
+	 * @return a hash code for the long
+	 *
+	 * @see Long#hashCode()
+	 */
+	public static int hashCode(long lng) {
+		return (int) (lng ^ (lng >>> 32));
+	}
+
+
+	//---------------------------------------------------------------------
+	// Convenience methods for toString output
+	//---------------------------------------------------------------------
+
+	/**
+	 * Return a String representation of an object's overall identity.
+	 * @param obj the object (may be {@code null})
+	 * @return the object's identity as String representation,
+	 * or an empty String if the object was {@code null}
+	 */
+	public static String identityToString(Object obj) {
+		if (obj == null) {
+			return EMPTY_STRING;
+		}
+		return obj.getClass().getName() + "@" + getIdentityHexString(obj);
+	}
+
+	/**
+	 * Return a hex String form of an object's identity hash code.
+	 * @param obj the object
+	 * @return the object's identity code in hex notation
+	 */
+	public static String getIdentityHexString(Object obj) {
+		return Integer.toHexString(System.identityHashCode(obj));
+	}
+
+	/**
+	 * Return a content-based String representation if {@code obj} is
+	 * not {@code null}; otherwise returns an empty String.
+	 * <p>Differs from {@link #nullSafeToString(Object)} in that it returns
+	 * an empty String rather than "null" for a {@code null} value.
+	 * @param obj the object to build a display String for
+	 * @return a display String representation of {@code obj}
+	 * @see #nullSafeToString(Object)
+	 */
+	public static String getDisplayString(Object obj) {
+		if (obj == null) {
+			return EMPTY_STRING;
+		}
+		return nullSafeToString(obj);
+	}
+
+	/**
+	 * Determine the class name for the given object.
+	 * <p>Returns {@code "null"} if {@code obj} is {@code null}.
+	 * @param obj the object to introspect (may be {@code null})
+	 * @return the corresponding class name
+	 */
+	public static String nullSafeClassName(Object obj) {
+		return (obj != null ? obj.getClass().getName() : NULL_STRING);
+	}
+
+	/**
+	 * Return a String representation of the specified Object.
+	 * <p>Builds a String representation of the contents in case of an array.
+	 * Returns {@code "null"} if {@code obj} is {@code null}.
+	 * @param obj the object to build a String representation for
+	 * @return a String representation of {@code obj}
+	 */
+	public static String nullSafeToString(Object obj) {
+		if (obj == null) {
+			return NULL_STRING;
+		}
+		if (obj instanceof String) {
+			return (String) obj;
+		}
+		if (obj instanceof Object[]) {
+			return nullSafeToString((Object[]) obj);
+		}
+		if (obj instanceof boolean[]) {
+			return nullSafeToString((boolean[]) obj);
+		}
+		if (obj instanceof byte[]) {
+			return nullSafeToString((byte[]) obj);
+		}
+		if (obj instanceof char[]) {
+			return nullSafeToString((char[]) obj);
+		}
+		if (obj instanceof double[]) {
+			return nullSafeToString((double[]) obj);
+		}
+		if (obj instanceof float[]) {
+			return nullSafeToString((float[]) obj);
+		}
+		if (obj instanceof int[]) {
+			return nullSafeToString((int[]) obj);
+		}
+		if (obj instanceof long[]) {
+			return nullSafeToString((long[]) obj);
+		}
+		if (obj instanceof short[]) {
+			return nullSafeToString((short[]) obj);
+		}
+		String str = obj.toString();
+		return (str != null ? str : EMPTY_STRING);
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(Object[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+			sb.append(String.valueOf(array[i]));
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(boolean[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+
+			sb.append(array[i]);
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(byte[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+			sb.append(array[i]);
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(char[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+			sb.append("'").append(array[i]).append("'");
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(double[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+
+			sb.append(array[i]);
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(float[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+
+			sb.append(array[i]);
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(int[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+			sb.append(array[i]);
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(long[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+			sb.append(array[i]);
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+	/**
+	 * Return a String representation of the contents of the specified array. <p>The String
+	 * representation consists of a list of the array's elements, enclosed in curly braces
+	 * (<code>"{}"</code>). Adjacent elements are separated by the characters {@code ", "}
+	 * (a comma followed by a space). Returns {@code "null"} if {@code array} is {@code null}.
+	 *
+	 * @param array the array to build a String representation for
+	 *
+	 * @return a String representation of {@code array}
+	 */
+	public static String nullSafeToString(short[] array) {
+		if (array == null) {
+			return NULL_STRING;
+		}
+		int length = array.length;
+		if (length == 0) {
+			return EMPTY_ARRAY;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < length; i++) {
+			if (i == 0) {
+				sb.append(ARRAY_START);
+			}
+			else {
+				sb.append(ARRAY_ELEMENT_SEPARATOR);
+			}
+			sb.append(array[i]);
+		}
+		sb.append(ARRAY_END);
+		return sb.toString();
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/util/PartitionedReferencePile.java b/reactor-core/src/main/java/reactor/util/PartitionedReferencePile.java
new file mode 100644
index 0000000..26e5df3
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/PartitionedReferencePile.java
@@ -0,0 +1,134 @@
+package reactor.util;
+
+import com.gs.collections.api.block.function.Function0;
+import com.gs.collections.api.block.procedure.Procedure;
+import com.gs.collections.api.block.procedure.Procedure2;
+import com.gs.collections.api.map.MutableMap;
+import com.gs.collections.api.multimap.MutableMultimap;
+import com.gs.collections.api.set.ImmutableSet;
+import com.gs.collections.impl.list.mutable.FastList;
+import com.gs.collections.impl.map.mutable.UnifiedMap;
+import com.gs.collections.impl.set.mutable.UnifiedSet;
+import reactor.function.Supplier;
+
+import javax.annotation.concurrent.ThreadSafe;
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author Jon Brisbin
+ */
+ at ThreadSafe
+public class PartitionedReferencePile<T> implements Supplier<T>, Iterable<T> {
+
+	private final int size;
+	private final Function0<FastList<T>> preAllocatedListFn = new Function0<FastList<T>>() {
+		@Override
+		public FastList<T> value() {
+			FastList<T> vals = FastList.newList(size);
+			for (int i = 0; i < size; i++) {
+				vals.add(factory.get());
+			}
+			return vals;
+		}
+	};
+	private final Supplier<T> factory;
+	private final MutableMap<Long, FastList<T>>        partitions      = UnifiedMap.newMap();
+	private final MutableMap<Long, AtomicInteger>      nextAvailable   = UnifiedMap.newMap();
+	private final Function0<AtomicInteger>             atomicIntegerFn = new Function0<AtomicInteger>() {
+		@Override
+		public AtomicInteger value() {
+			return new AtomicInteger(-1);
+		}
+	};
+	private final Procedure2<FastList<T>, FastList<T>> zipFn           = new Procedure2<FastList<T>, FastList<T>>() {
+		@Override
+		public void value(FastList<T> vals, FastList<T> agg) {
+			agg.addAll(vals);
+		}
+	};
+
+	public PartitionedReferencePile(Supplier<T> factory) {
+		this(1024, factory);
+	}
+
+	public PartitionedReferencePile(int size, Supplier<T> factory) {
+		this.size = size;
+		this.factory = factory;
+	}
+
+	@Override
+	public T get() {
+		Long threadId = Thread.currentThread().getId();
+		int nextAvail = nextAvailable.getIfAbsentPut(threadId, atomicIntegerFn).incrementAndGet();
+		FastList<T> vals = partitions.getIfAbsentPut(threadId, preAllocatedListFn);
+		int len = vals.size();
+		if (len == nextAvail) {
+			vals.addAll(preAllocatedListFn.value());
+		}
+
+		return vals.get(nextAvail);
+	}
+
+	public ImmutableSet<T> collect() {
+		final UnifiedSet<T> vals = UnifiedSet.newSet();
+		partitions.keysView().forEach(new Procedure<Long>() {
+			@Override
+			public void value(Long threadId) {
+				Iterator<T> iter = iteratorFor(threadId);
+				while (iter.hasNext()) {
+					vals.add(iter.next());
+				}
+			}
+		});
+		return vals.toImmutable();
+	}
+
+	@Override
+	public Iterator<T> iterator() {
+		return iteratorFor(Thread.currentThread().getId());
+	}
+
+	@Override
+	public String toString() {
+		final StringBuilder sb = new StringBuilder("PartitionedReferencePile{\n");
+		partitions.forEachKeyValue(new Procedure2<Long, FastList<T>>() {
+			@Override
+			public void value(Long threadId, FastList<T> vals) {
+				sb.append("\tthread:")
+				  .append(threadId)
+				  .append("=")
+				  .append(vals.getFirst().getClass().getSimpleName())
+				  .append("[").append(vals.size()).append("],\n");
+			}
+		});
+		sb.append("}");
+		return sb.toString();
+	}
+
+	private Iterator<T> iteratorFor(Long threadId) {
+		AtomicInteger nextAvail = nextAvailable.getIfAbsentPut(threadId, atomicIntegerFn);
+		final FastList<T> vals = partitions.getIfAbsentPut(threadId, preAllocatedListFn);
+		final int end = nextAvail.getAndSet(-1);
+
+		return new Iterator<T>() {
+			int currIdx = 0;
+
+			@Override
+			public boolean hasNext() {
+				return currIdx <= end;
+			}
+
+			@Override
+			public T next() {
+				int idx = currIdx++;
+				return vals.get(idx);
+			}
+
+			@Override
+			public void remove() {
+				throw new IllegalStateException("PartitionedReferencePile Iterators are read-only");
+			}
+		};
+	}
+}
diff --git a/reactor-core/src/main/java/reactor/util/StringUtils.java b/reactor-core/src/main/java/reactor/util/StringUtils.java
new file mode 100644
index 0000000..7df74ec
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/StringUtils.java
@@ -0,0 +1,1216 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+/**
+ * Miscellaneous {@link String} utility methods.
+ *
+ * <p>Mainly for internal use within the framework; consider
+ * <a href="http://jakarta.apache.org/commons/lang/">Jakarta's Commons Lang</a> for a more
+ * comprehensive suite of String utilities.
+ *
+ * <p>This class delivers some simple functionality that should really be provided by the
+ * core Java {@code String} and {@link StringBuilder} classes, such as the ability to
+ * {@link #replace} all occurrences of a given substring in a target string. It also
+ * provides easy-to-use methods to convert between delimited strings, such as CSV strings,
+ * and collections and arrays.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rob Harrop
+ * @author Rick Evans
+ * @author Arjen Poutsma
+ */
+public abstract class StringUtils {
+
+	private static final String FOLDER_SEPARATOR = "/";
+
+	private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
+
+	private static final String TOP_PATH = "..";
+
+	private static final String CURRENT_PATH = ".";
+
+	private static final char EXTENSION_SEPARATOR = '.';
+
+
+	//---------------------------------------------------------------------
+	// General convenience methods for working with Strings
+	//---------------------------------------------------------------------
+
+	/**
+	 * Check whether the given String is empty.
+	 * <p>This method accepts any Object as an argument, comparing it to {@code null} and the
+	 * empty String. As a consequence, this method will never return {@code true} for a non-null
+	 * non-String object.
+	 * <p>The Object signature is useful for general attribute handling code that commonly deals
+	 * with Strings but generally has to iterate over Objects since attributes may e.g. be primitive
+	 * value objects as well.
+	 *
+	 * @param str the candidate String
+	 *
+	 * @return {@code true} if the object is null or an empty string, {@code false} otherwise
+	 */
+	public static boolean isEmpty(Object str) {
+		return (str == null || "".equals(str));
+	}
+
+	/**
+	 * Check that the given CharSequence is neither {@code null} nor of length 0. Note: Will return
+	 * {@code true} for a CharSequence that purely consists of whitespace.
+	 *
+	 * <p><pre class="code">
+	 * StringUtils.hasLength(null) = false
+	 * StringUtils.hasLength("") = false
+	 * StringUtils.hasLength(" ") = true
+	 * StringUtils.hasLength("Hello") = true
+	 * </pre>
+	 *
+	 * @param str the CharSequence to check (may be {@code null})
+	 *
+	 * @return {@code true} if the CharSequence is not null and has length
+	 *
+	 * @see #hasText(String)
+	 */
+	public static boolean hasLength(CharSequence str) {
+		return (str != null && str.length() > 0);
+	}
+
+	/**
+	 * Check that the given String is neither {@code null} nor of length 0. Note: Will return {@code
+	 * true} for a String that purely consists of whitespace.
+	 *
+	 * @param str the String to check (may be {@code null})
+	 *
+	 * @return {@code true} if the String is not null and has length
+	 *
+	 * @see #hasLength(CharSequence)
+	 */
+	public static boolean hasLength(String str) {
+		return hasLength((CharSequence) str);
+	}
+
+	/**
+	 * Check whether the given CharSequence has actual text. More specifically, returns {@code
+	 * true} if the string not {@code null}, its length is greater than 0, and it contains at
+	 * least one non-whitespace character.
+	 *
+	 * <p><pre class="code">
+	 * StringUtils.hasText(null) = false
+	 * StringUtils.hasText("") = false
+	 * StringUtils.hasText(" ") = false
+	 * StringUtils.hasText("12345") = true
+	 * StringUtils.hasText(" 12345 ") = true
+	 * </pre>
+	 *
+	 * @param str the CharSequence to check (may be {@code null})
+	 *
+	 * @return {@code true} if the CharSequence is not {@code null}, its length is greater than
+	 *         0, and it does not contain whitespace only
+	 *
+	 * @see Character#isWhitespace
+	 */
+	public static boolean hasText(CharSequence str) {
+		if (!hasLength(str)) {
+			return false;
+		}
+		int strLen = str.length();
+		for (int i = 0; i < strLen; i++) {
+			if (!Character.isWhitespace(str.charAt(i))) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Check whether the given String has actual text. More specifically, returns {@code true} if
+	 * the string not {@code null}, its length is greater than 0, and it contains at least one
+	 * non-whitespace character.
+	 *
+	 * @param str the String to check (may be {@code null})
+	 *
+	 * @return {@code true} if the String is not {@code null}, its length is greater than 0, and it
+	 *         does not contain whitespace only
+	 *
+	 * @see #hasText(CharSequence)
+	 */
+	public static boolean hasText(String str) {
+		return hasText((CharSequence) str);
+	}
+
+	/**
+	 * Check whether the given CharSequence contains any whitespace characters.
+	 *
+	 * @param str the CharSequence to check (may be {@code null})
+	 *
+	 * @return {@code true} if the CharSequence is not empty and contains at least 1 whitespace
+	 *         character
+	 *
+	 * @see Character#isWhitespace
+	 */
+	public static boolean containsWhitespace(CharSequence str) {
+		if (!hasLength(str)) {
+			return false;
+		}
+		int strLen = str.length();
+		for (int i = 0; i < strLen; i++) {
+			if (Character.isWhitespace(str.charAt(i))) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Check whether the given String contains any whitespace characters.
+	 *
+	 * @param str the String to check (may be {@code null})
+	 *
+	 * @return {@code true} if the String is not empty and contains at least 1 whitespace character
+	 *
+	 * @see #containsWhitespace(CharSequence)
+	 */
+	public static boolean containsWhitespace(String str) {
+		return containsWhitespace((CharSequence) str);
+	}
+
+	/**
+	 * Trim leading and trailing whitespace from the given String.
+	 *
+	 * @param str the String to check
+	 *
+	 * @return the trimmed String
+	 *
+	 * @see java.lang.Character#isWhitespace
+	 */
+	public static String trimWhitespace(String str) {
+		if (!hasLength(str)) {
+			return str;
+		}
+		StringBuilder sb = new StringBuilder(str);
+		while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
+			sb.deleteCharAt(0);
+		}
+		while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
+			sb.deleteCharAt(sb.length() - 1);
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Trim <i>all</i> whitespace from the given String: leading, trailing, and inbetween
+	 * characters.
+	 *
+	 * @param str the String to trim
+	 *
+	 * @return the trimmed String
+	 *
+	 * @see java.lang.Character#isWhitespace
+	 */
+	public static String trimAllWhitespace(String str) {
+		if (!hasLength(str)) {
+			return str;
+		}
+		StringBuilder sb = new StringBuilder(str);
+		int index = 0;
+		while (sb.length() > index) {
+			if (Character.isWhitespace(sb.charAt(index))) {
+				sb.deleteCharAt(index);
+			}
+			else {
+				index++;
+			}
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Trim leading whitespace from the given String.
+	 *
+	 * @param str the String to trim
+	 *
+	 * @return the trimmed String
+	 *
+	 * @see java.lang.Character#isWhitespace
+	 */
+	public static String trimLeadingWhitespace(String str) {
+		if (!hasLength(str)) {
+			return str;
+		}
+		StringBuilder sb = new StringBuilder(str);
+		while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
+			sb.deleteCharAt(0);
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Trim trailing whitespace from the given String.
+	 *
+	 * @param str the String to trim
+	 *
+	 * @return the trimmed String
+	 *
+	 * @see java.lang.Character#isWhitespace
+	 */
+	public static String trimTrailingWhitespace(String str) {
+		if (!hasLength(str)) {
+			return str;
+		}
+		StringBuilder sb = new StringBuilder(str);
+		while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
+			sb.deleteCharAt(sb.length() - 1);
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Trim all occurrences of the supplied leading character from the given String.
+	 *
+	 * @param str the String to check
+	 * @param leadingCharacter the leading character to be trimmed
+	 *
+	 * @return the trimmed String
+	 */
+	public static String trimLeadingCharacter(String str, char leadingCharacter) {
+		if (!hasLength(str)) {
+			return str;
+		}
+		StringBuilder sb = new StringBuilder(str);
+		while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) {
+			sb.deleteCharAt(0);
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Trim all occurrences of the supplied trailing character from the given String.
+	 *
+	 * @param str the String to check
+	 * @param trailingCharacter the trailing character to be trimmed
+	 *
+	 * @return the trimmed String
+	 */
+	public static String trimTrailingCharacter(String str, char trailingCharacter) {
+		if (!hasLength(str)) {
+			return str;
+		}
+		StringBuilder sb = new StringBuilder(str);
+		while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) {
+			sb.deleteCharAt(sb.length() - 1);
+		}
+		return sb.toString();
+	}
+
+
+	/**
+	 * Test if the given String starts with the specified prefix, ignoring upper/lower case.
+	 *
+	 * @param str the String to check
+	 * @param prefix the prefix to look for
+	 *
+	 * @return {@code true} if the string starts with the given prefix, {@code false} otherwise
+	 *
+	 * @see java.lang.String#startsWith
+	 */
+	public static boolean startsWithIgnoreCase(String str, String prefix) {
+		if (str == null || prefix == null) {
+			return false;
+		}
+		if (str.startsWith(prefix)) {
+			return true;
+		}
+		if (str.length() < prefix.length()) {
+			return false;
+		}
+		String lcStr = str.substring(0, prefix.length()).toLowerCase();
+		String lcPrefix = prefix.toLowerCase();
+		return lcStr.equals(lcPrefix);
+	}
+
+	/**
+	 * Test if the given String ends with the specified suffix, ignoring upper/lower case.
+	 *
+	 * @param str the String to check
+	 * @param suffix the suffix to look for
+	 *
+	 * @return {@code true} if the string ends with the given suffix, {@code false} otherwise
+	 *
+	 * @see java.lang.String#endsWith
+	 */
+	public static boolean endsWithIgnoreCase(String str, String suffix) {
+		if (str == null || suffix == null) {
+			return false;
+		}
+		if (str.endsWith(suffix)) {
+			return true;
+		}
+		if (str.length() < suffix.length()) {
+			return false;
+		}
+
+		String lcStr = str.substring(str.length() - suffix.length()).toLowerCase();
+		String lcSuffix = suffix.toLowerCase();
+		return lcStr.equals(lcSuffix);
+	}
+
+	/**
+	 * Test whether the given string matches the given substring at the given index.
+	 *
+	 * @param str the original string (or StringBuilder)
+	 * @param index the index in the original string to start matching against
+	 * @param substring the substring to match at the given index
+	 *
+	 * @return {@code true} if the string matches, {@code false} otherwise
+	 */
+	public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
+		for (int j = 0; j < substring.length(); j++) {
+			int i = index + j;
+			if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Count the occurrences of the substring in string s.
+	 *
+	 * @param str string to search in. Return 0 if this is null.
+	 * @param sub string to search for. Return 0 if this is null.
+	 *
+	 * @return the number of ocurrences
+	 */
+	public static int countOccurrencesOf(String str, String sub) {
+		if (str == null || sub == null || str.length() == 0 || sub.length() == 0) {
+			return 0;
+		}
+		int count = 0;
+		int pos = 0;
+		int idx;
+		while ((idx = str.indexOf(sub, pos)) != -1) {
+			++count;
+			pos = idx + sub.length();
+		}
+		return count;
+	}
+
+	/**
+	 * Replace all occurrences of a substring within a string with another string.
+	 *
+	 * @param inString String to examine
+	 * @param oldPattern String to replace
+	 * @param newPattern String to insert
+	 *
+	 * @return a String with the replacements
+	 */
+	public static String replace(String inString, String oldPattern, String newPattern) {
+		if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
+			return inString;
+		}
+		StringBuilder sb = new StringBuilder();
+		int pos = 0; // our position in the old string
+		int index = inString.indexOf(oldPattern);
+		// the index of an occurrence we've found, or -1
+		int patLen = oldPattern.length();
+		while (index >= 0) {
+			sb.append(inString.substring(pos, index));
+			sb.append(newPattern);
+			pos = index + patLen;
+			index = inString.indexOf(oldPattern, pos);
+		}
+		sb.append(inString.substring(pos));
+		// remember to append any characters to the right of a match
+		return sb.toString();
+	}
+
+	/**
+	 * Delete all occurrences of the given substring.
+	 *
+	 * @param inString the original String
+	 * @param pattern the pattern to delete all occurrences of
+	 *
+	 * @return the resulting String
+	 */
+	public static String delete(String inString, String pattern) {
+		return replace(inString, pattern, "");
+	}
+
+	/**
+	 * Delete any character in a given String.
+	 *
+	 * @param inString the original String
+	 * @param charsToDelete a set of characters to delete. E.g. "az\n" will delete 'a's, 'z's
+	 *        and new lines.
+	 *
+	 * @return the resulting String
+	 */
+	public static String deleteAny(String inString, String charsToDelete) {
+		if (!hasLength(inString) || !hasLength(charsToDelete)) {
+			return inString;
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < inString.length(); i++) {
+			char c = inString.charAt(i);
+			if (charsToDelete.indexOf(c) == -1) {
+				sb.append(c);
+			}
+		}
+		return sb.toString();
+	}
+
+
+	//---------------------------------------------------------------------
+	// Convenience methods for working with formatted Strings
+	//---------------------------------------------------------------------
+
+	/**
+	 * Quote the given String with single quotes.
+	 *
+	 * @param str the input String (e.g. "myString")
+	 *
+	 * @return the quoted String (e.g. "'myString'"), or {@code null} if the input was {@code null}
+	 */
+	public static String quote(String str) {
+		return (str != null ? "'" + str + "'" : null);
+	}
+
+	/**
+	 * Turn the given Object into a String with single quotes if it is a String; keeping the Object
+	 * as-is else.
+	 *
+	 * @param obj the input Object (e.g. "myString")
+	 *
+	 * @return the quoted String (e.g. "'myString'"), or the input object as-is if not a String
+	 */
+	public static Object quoteIfString(Object obj) {
+		return (obj instanceof String ? quote((String) obj) : obj);
+	}
+
+	/**
+	 * Unqualify a string qualified by a '.' dot character. For example, "this.name.is.qualified",
+	 * returns "qualified".
+	 *
+	 * @param qualifiedName the qualified name
+	 *
+	 * @return the unqualified name
+	 */
+	public static String unqualify(String qualifiedName) {
+		return unqualify(qualifiedName, '.');
+	}
+
+	/**
+	 * Unqualify a string qualified by a separator character. For example, "this:name:is:qualified"
+	 * returns "qualified" if using a ':' separator.
+	 *
+	 * @param qualifiedName the qualified name
+	 * @param separator the separator
+	 *
+	 * @return the unqualified string
+	 */
+	public static String unqualify(String qualifiedName, char separator) {
+		return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1);
+	}
+
+	/**
+	 * Capitalize a {@code String}, changing the first letter to
+	 * upper case as per {@link Character#toUpperCase(char)}.
+	 * No other letters are changed.
+	 * @param str the String to capitalize, may be {@code null}
+	 * @return the capitalized String, {@code null} if null
+	 */
+	public static String capitalize(String str) {
+		return changeFirstCharacterCase(str, true);
+	}
+
+	/**
+	 * Uncapitalize a {@code String}, changing the first letter to
+	 * lower case as per {@link Character#toLowerCase(char)}.
+	 * No other letters are changed.
+	 * @param str the String to uncapitalize, may be {@code null}
+	 * @return the uncapitalized String, {@code null} if null
+	 */
+	public static String uncapitalize(String str) {
+		return changeFirstCharacterCase(str, false);
+	}
+
+	private static String changeFirstCharacterCase(String str, boolean capitalize) {
+		if (str == null || str.length() == 0) {
+			return str;
+		}
+		StringBuilder sb = new StringBuilder(str.length());
+		if (capitalize) {
+			sb.append(Character.toUpperCase(str.charAt(0)));
+		}
+		else {
+			sb.append(Character.toLowerCase(str.charAt(0)));
+		}
+		sb.append(str.substring(1));
+		return sb.toString();
+	}
+
+	/**
+	 * Extract the filename from the given path,
+	 * e.g. "mypath/myfile.txt" -> "myfile.txt".
+	 * @param path the file path (may be {@code null})
+	 * @return the extracted filename, or {@code null} if none
+	 */
+	public static String getFilename(String path) {
+		if (path == null) {
+			return null;
+		}
+		int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+		return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path);
+	}
+
+	/**
+	 * Extract the filename extension from the given path,
+	 * e.g. "mypath/myfile.txt" -> "txt".
+	 * @param path the file path (may be {@code null})
+	 * @return the extracted filename extension, or {@code null} if none
+	 */
+	public static String getFilenameExtension(String path) {
+		if (path == null) {
+			return null;
+		}
+		int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
+		if (extIndex == -1) {
+			return null;
+		}
+		int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+		if (folderIndex > extIndex) {
+			return null;
+		}
+		return path.substring(extIndex + 1);
+	}
+
+	/**
+	 * Strip the filename extension from the given path,
+	 * e.g. "mypath/myfile.txt" -> "mypath/myfile".
+	 * @param path the file path (may be {@code null})
+	 * @return the path with stripped filename extension,
+	 * or {@code null} if none
+	 */
+	public static String stripFilenameExtension(String path) {
+		if (path == null) {
+			return null;
+		}
+		int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
+		if (extIndex == -1) {
+			return path;
+		}
+		int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+		if (folderIndex > extIndex) {
+			return path;
+		}
+		return path.substring(0, extIndex);
+	}
+
+	/**
+	 * Apply the given relative path to the given path,
+	 * assuming standard Java folder separation (i.e. "/" separators).
+	 * @param path the path to start from (usually a full file path)
+	 * @param relativePath the relative path to apply
+	 * (relative to the full file path above)
+	 * @return the full file path that results from applying the relative path
+	 */
+	public static String applyRelativePath(String path, String relativePath) {
+		int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+		if (separatorIndex != -1) {
+			String newPath = path.substring(0, separatorIndex);
+			if (!relativePath.startsWith(FOLDER_SEPARATOR)) {
+				newPath += FOLDER_SEPARATOR;
+			}
+			return newPath + relativePath;
+		}
+		else {
+			return relativePath;
+		}
+	}
+
+	/**
+	 * Normalize the path by suppressing sequences like "path/.." and
+	 * inner simple dots.
+	 * <p>The result is convenient for path comparison. For other uses,
+	 * notice that Windows separators ("\") are replaced by simple slashes.
+	 * @param path the original path
+	 * @return the normalized path
+	 */
+	public static String cleanPath(String path) {
+		if (path == null) {
+			return null;
+		}
+		String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
+
+		// Strip prefix from path to analyze, to not treat it as part of the
+		// first path element. This is necessary to correctly parse paths like
+		// "file:core/../core/io/Resource.class", where the ".." should just
+		// strip the first "core" directory while keeping the "file:" prefix.
+		int prefixIndex = pathToUse.indexOf(":");
+		String prefix = "";
+		if (prefixIndex != -1) {
+			prefix = pathToUse.substring(0, prefixIndex + 1);
+			pathToUse = pathToUse.substring(prefixIndex + 1);
+		}
+		if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
+			prefix = prefix + FOLDER_SEPARATOR;
+			pathToUse = pathToUse.substring(1);
+		}
+
+		String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
+		List<String> pathElements = new LinkedList<String>();
+		int tops = 0;
+
+		for (int i = pathArray.length - 1; i >= 0; i--) {
+			String element = pathArray[i];
+			if (CURRENT_PATH.equals(element)) {
+				// Points to current directory - drop it.
+			}
+			else if (TOP_PATH.equals(element)) {
+				// Registering top path found.
+				tops++;
+			}
+			else {
+				if (tops > 0) {
+					// Merging path element with element corresponding to top path.
+					tops--;
+				}
+				else {
+					// Normal path element found.
+					pathElements.add(0, element);
+				}
+			}
+		}
+
+		// Remaining top paths need to be retained.
+		for (int i = 0; i < tops; i++) {
+			pathElements.add(0, TOP_PATH);
+		}
+
+		return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
+	}
+
+	/**
+	 * Compare two paths after normalization of them.
+	 * @param path1 first path for comparison
+	 * @param path2 second path for comparison
+	 * @return whether the two paths are equivalent after normalization
+	 */
+	public static boolean pathEquals(String path1, String path2) {
+		return cleanPath(path1).equals(cleanPath(path2));
+	}
+
+	/**
+	 * Parse the given {@code localeString} value into a {@link Locale}.
+	 * <p>This is the inverse operation of {@link Locale#toString Locale's toString}.
+	 * @param localeString the locale string, following {@code Locale's}
+	 * {@code toString()} format ("en", "en_UK", etc);
+	 * also accepts spaces as separators, as an alternative to underscores
+	 * @return a corresponding {@code Locale} instance
+	 */
+	public static Locale parseLocaleString(String localeString) {
+		String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);
+		String language = (parts.length > 0 ? parts[0] : "");
+		String country = (parts.length > 1 ? parts[1] : "");
+		validateLocalePart(language);
+		validateLocalePart(country);
+		String variant = "";
+		if (parts.length >= 2) {
+			// There is definitely a variant, and it is everything after the country
+			// code sans the separator between the country code and the variant.
+			int endIndexOfCountryCode = localeString.lastIndexOf(country) + country.length();
+			// Strip off any leading '_' and whitespace, what's left is the variant.
+			variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));
+			if (variant.startsWith("_")) {
+				variant = trimLeadingCharacter(variant, '_');
+			}
+		}
+		return (language.length() > 0 ? new Locale(language, country, variant) : null);
+	}
+
+	private static void validateLocalePart(String localePart) {
+		for (int i = 0; i < localePart.length(); i++) {
+			char ch = localePart.charAt(i);
+			if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) {
+				throw new IllegalArgumentException(
+						"Locale part \"" + localePart + "\" contains invalid characters");
+			}
+		}
+	}
+
+	/**
+	 * Determine the RFC 3066 compliant language tag,
+	 * as used for the HTTP "Accept-Language" header.
+	 * @param locale the Locale to transform to a language tag
+	 * @return the RFC 3066 compliant language tag as String
+	 */
+	public static String toLanguageTag(Locale locale) {
+		return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");
+	}
+
+
+	//---------------------------------------------------------------------
+	// Convenience methods for working with String arrays
+	//---------------------------------------------------------------------
+
+	/**
+	 * Append the given String to the given String array, returning a new array
+	 * consisting of the input array contents plus the given String.
+	 * @param array the array to append to (can be {@code null})
+	 * @param str the String to append
+	 * @return the new array (never {@code null})
+	 */
+	public static String[] addStringToArray(String[] array, String str) {
+		if (ObjectUtils.isEmpty(array)) {
+			return new String[] {str};
+		}
+		String[] newArr = new String[array.length + 1];
+		System.arraycopy(array, 0, newArr, 0, array.length);
+		newArr[array.length] = str;
+		return newArr;
+	}
+
+	/**
+	 * Concatenate the given String arrays into one,
+	 * with overlapping array elements included twice.
+	 * <p>The order of elements in the original arrays is preserved.
+	 * @param array1 the first array (can be {@code null})
+	 * @param array2 the second array (can be {@code null})
+	 * @return the new array ({@code null} if both given arrays were {@code null})
+	 */
+	public static String[] concatenateStringArrays(String[] array1, String[] array2) {
+		if (ObjectUtils.isEmpty(array1)) {
+			return array2;
+		}
+		if (ObjectUtils.isEmpty(array2)) {
+			return array1;
+		}
+		String[] newArr = new String[array1.length + array2.length];
+		System.arraycopy(array1, 0, newArr, 0, array1.length);
+		System.arraycopy(array2, 0, newArr, array1.length, array2.length);
+		return newArr;
+	}
+
+	/**
+	 * Merge the given String arrays into one, with overlapping
+	 * array elements only included once.
+	 * <p>The order of elements in the original arrays is preserved
+	 * (with the exception of overlapping elements, which are only
+	 * included on their first occurrence).
+	 * @param array1 the first array (can be {@code null})
+	 * @param array2 the second array (can be {@code null})
+	 * @return the new array ({@code null} if both given arrays were {@code null})
+	 */
+	public static String[] mergeStringArrays(String[] array1, String[] array2) {
+		if (ObjectUtils.isEmpty(array1)) {
+			return array2;
+		}
+		if (ObjectUtils.isEmpty(array2)) {
+			return array1;
+		}
+		List<String> result = new ArrayList<String>();
+		result.addAll(Arrays.asList(array1));
+		for (String str : array2) {
+			if (!result.contains(str)) {
+				result.add(str);
+			}
+		}
+		return toStringArray(result);
+	}
+
+	/**
+	 * Turn given source String array into sorted array.
+	 * @param array the source array
+	 * @return the sorted array (never {@code null})
+	 */
+	public static String[] sortStringArray(String[] array) {
+		if (ObjectUtils.isEmpty(array)) {
+			return new String[0];
+		}
+		Arrays.sort(array);
+		return array;
+	}
+
+	/**
+	 * Copy the given Collection into a String array.
+	 * The Collection must contain String elements only.
+	 * @param collection the Collection to copy
+	 * @return the String array ({@code null} if the passed-in
+	 * Collection was {@code null})
+	 */
+	public static String[] toStringArray(Collection<String> collection) {
+		if (collection == null) {
+			return null;
+		}
+		return collection.toArray(new String[collection.size()]);
+	}
+
+	/**
+	 * Copy the given Enumeration into a String array.
+	 * The Enumeration must contain String elements only.
+	 * @param enumeration the Enumeration to copy
+	 * @return the String array ({@code null} if the passed-in
+	 * Enumeration was {@code null})
+	 */
+	public static String[] toStringArray(Enumeration<String> enumeration) {
+		if (enumeration == null) {
+			return null;
+		}
+		List<String> list = Collections.list(enumeration);
+		return list.toArray(new String[list.size()]);
+	}
+
+	/**
+	 * Trim the elements of the given String array,
+	 * calling {@code String.trim()} on each of them.
+	 * @param array the original String array
+	 * @return the resulting array (of the same size) with trimmed elements
+	 */
+	public static String[] trimArrayElements(String[] array) {
+		if (ObjectUtils.isEmpty(array)) {
+			return new String[0];
+		}
+		String[] result = new String[array.length];
+		for (int i = 0; i < array.length; i++) {
+			String element = array[i];
+			result[i] = (element != null ? element.trim() : null);
+		}
+		return result;
+	}
+
+	/**
+	 * Remove duplicate Strings from the given array.
+	 * Also sorts the array, as it uses a TreeSet.
+	 * @param array the String array
+	 * @return an array without duplicates, in natural sort order
+	 */
+	public static String[] removeDuplicateStrings(String[] array) {
+		if (ObjectUtils.isEmpty(array)) {
+			return array;
+		}
+		Set<String> set = new TreeSet<String>();
+		for (String element : array) {
+			set.add(element);
+		}
+		return toStringArray(set);
+	}
+
+	/**
+	 * Split a String at the first occurrence of the delimiter.
+	 * Does not include the delimiter in the result.
+	 * @param toSplit the string to split
+	 * @param delimiter to split the string up with
+	 * @return a two element array with index 0 being before the delimiter, and
+	 * index 1 being after the delimiter (neither element includes the delimiter);
+	 * or {@code null} if the delimiter wasn't found in the given input String
+	 */
+	public static String[] split(String toSplit, String delimiter) {
+		if (!hasLength(toSplit) || !hasLength(delimiter)) {
+			return null;
+		}
+		int offset = toSplit.indexOf(delimiter);
+		if (offset < 0) {
+			return null;
+		}
+		String beforeDelimiter = toSplit.substring(0, offset);
+		String afterDelimiter = toSplit.substring(offset + delimiter.length());
+		return new String[] {beforeDelimiter, afterDelimiter};
+	}
+
+	/**
+	 * Take an array Strings and split each element based on the given delimiter.
+	 * A {@code Properties} instance is then generated, with the left of the
+	 * delimiter providing the key, and the right of the delimiter providing the value.
+	 * <p>Will trim both the key and value before adding them to the
+	 * {@code Properties} instance.
+	 * @param array the array to process
+	 * @param delimiter to split each element using (typically the equals symbol)
+	 * @return a {@code Properties} instance representing the array contents,
+	 * or {@code null} if the array to process was null or empty
+	 */
+	public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) {
+		return splitArrayElementsIntoProperties(array, delimiter, null);
+	}
+
+	/**
+	 * Take an array Strings and split each element based on the given delimiter.
+	 * A {@code Properties} instance is then generated, with the left of the
+	 * delimiter providing the key, and the right of the delimiter providing the value.
+	 * <p>Will trim both the key and value before adding them to the
+	 * {@code Properties} instance.
+	 * @param array the array to process
+	 * @param delimiter to split each element using (typically the equals symbol)
+	 * @param charsToDelete one or more characters to remove from each element
+	 * prior to attempting the split operation (typically the quotation mark
+	 * symbol), or {@code null} if no removal should occur
+	 * @return a {@code Properties} instance representing the array contents,
+	 * or {@code null} if the array to process was {@code null} or empty
+	 */
+	public static Properties splitArrayElementsIntoProperties(
+			String[] array, String delimiter, String charsToDelete) {
+
+		if (ObjectUtils.isEmpty(array)) {
+			return null;
+		}
+		Properties result = new Properties();
+		for (String element : array) {
+			if (charsToDelete != null) {
+				element = deleteAny(element, charsToDelete);
+			}
+			String[] splittedElement = split(element, delimiter);
+			if (splittedElement == null) {
+				continue;
+			}
+			result.setProperty(splittedElement[0].trim(), splittedElement[1].trim());
+		}
+		return result;
+	}
+
+	/**
+	 * Tokenize the given String into a String array via a StringTokenizer.
+	 * Trims tokens and omits empty tokens.
+	 * <p>The given delimiters string is supposed to consist of any number of
+	 * delimiter characters. Each of those characters can be used to separate
+	 * tokens. A delimiter is always a single character; for multi-character
+	 * delimiters, consider using {@code delimitedListToStringArray}
+	 * @param str the String to tokenize
+	 * @param delimiters the delimiter characters, assembled as String
+	 * (each of those characters is individually considered as delimiter).
+	 * @return an array of the tokens
+	 * @see java.util.StringTokenizer
+	 * @see String#trim()
+	 * @see #delimitedListToStringArray
+	 */
+	public static String[] tokenizeToStringArray(String str, String delimiters) {
+		return tokenizeToStringArray(str, delimiters, true, true);
+	}
+
+	/**
+	 * Tokenize the given String into a String array via a StringTokenizer.
+	 * <p>The given delimiters string is supposed to consist of any number of
+	 * delimiter characters. Each of those characters can be used to separate
+	 * tokens. A delimiter is always a single character; for multi-character
+	 * delimiters, consider using {@code delimitedListToStringArray}
+	 * @param str the String to tokenize
+	 * @param delimiters the delimiter characters, assembled as String
+	 * (each of those characters is individually considered as delimiter)
+	 * @param trimTokens trim the tokens via String's {@code trim}
+	 * @param ignoreEmptyTokens omit empty tokens from the result array
+	 * (only applies to tokens that are empty after trimming; StringTokenizer
+	 * will not consider subsequent delimiters as token in the first place).
+	 * @return an array of the tokens ({@code null} if the input String
+	 * was {@code null})
+	 * @see java.util.StringTokenizer
+	 * @see String#trim()
+	 * @see #delimitedListToStringArray
+	 */
+	public static String[] tokenizeToStringArray(
+			String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
+
+		if (str == null) {
+			return null;
+		}
+		StringTokenizer st = new StringTokenizer(str, delimiters);
+		List<String> tokens = new ArrayList<String>();
+		while (st.hasMoreTokens()) {
+			String token = st.nextToken();
+			if (trimTokens) {
+				token = token.trim();
+			}
+			if (!ignoreEmptyTokens || token.length() > 0) {
+				tokens.add(token);
+			}
+		}
+		return toStringArray(tokens);
+	}
+
+	/**
+	 * Take a String which is a delimited list and convert it to a String array.
+	 * <p>A single delimiter can consists of more than one character: It will still
+	 * be considered as single delimiter string, rather than as bunch of potential
+	 * delimiter characters - in contrast to {@code tokenizeToStringArray}.
+	 * @param str the input String
+	 * @param delimiter the delimiter between elements (this is a single delimiter,
+	 * rather than a bunch individual delimiter characters)
+	 * @return an array of the tokens in the list
+	 * @see #tokenizeToStringArray
+	 */
+	public static String[] delimitedListToStringArray(String str, String delimiter) {
+		return delimitedListToStringArray(str, delimiter, null);
+	}
+
+	/**
+	 * Take a String which is a delimited list and convert it to a String array.
+	 * <p>A single delimiter can consists of more than one character: It will still
+	 * be considered as single delimiter string, rather than as bunch of potential
+	 * delimiter characters - in contrast to {@code tokenizeToStringArray}.
+	 * @param str the input String
+	 * @param delimiter the delimiter between elements (this is a single delimiter,
+	 * rather than a bunch individual delimiter characters)
+	 * @param charsToDelete a set of characters to delete. Useful for deleting unwanted
+	 * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String.
+	 * @return an array of the tokens in the list
+	 * @see #tokenizeToStringArray
+	 */
+	public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) {
+		if (str == null) {
+			return new String[0];
+		}
+		if (delimiter == null) {
+			return new String[] {str};
+		}
+		List<String> result = new ArrayList<String>();
+		if ("".equals(delimiter)) {
+			for (int i = 0; i < str.length(); i++) {
+				result.add(deleteAny(str.substring(i, i + 1), charsToDelete));
+			}
+		}
+		else {
+			int pos = 0;
+			int delPos;
+			while ((delPos = str.indexOf(delimiter, pos)) != -1) {
+				result.add(deleteAny(str.substring(pos, delPos), charsToDelete));
+				pos = delPos + delimiter.length();
+			}
+			if (str.length() > 0 && pos <= str.length()) {
+				// Add rest of String, but not in case of empty input.
+				result.add(deleteAny(str.substring(pos), charsToDelete));
+			}
+		}
+		return toStringArray(result);
+	}
+
+	/**
+	 * Convert a CSV list into an array of Strings.
+	 * @param str the input String
+	 * @return an array of Strings, or the empty array in case of empty input
+	 */
+	public static String[] commaDelimitedListToStringArray(String str) {
+		return delimitedListToStringArray(str, ",");
+	}
+
+	/**
+	 * Convenience method to convert a CSV string list to a set.
+	 * Note that this will suppress duplicates.
+	 * @param str the input String
+	 * @return a Set of String entries in the list
+	 */
+	public static Set<String> commaDelimitedListToSet(String str) {
+		Set<String> set = new TreeSet<String>();
+		String[] tokens = commaDelimitedListToStringArray(str);
+		for (String token : tokens) {
+			set.add(token);
+		}
+		return set;
+	}
+
+	/**
+	 * Convenience method to return a Collection as a delimited (e.g. CSV)
+	 * String. E.g. useful for {@code toString()} implementations.
+	 * @param coll the Collection to display
+	 * @param delim the delimiter to use (probably a ",")
+	 * @param prefix the String to start each element with
+	 * @param suffix the String to end each element with
+	 * @return the delimited String
+	 */
+	public static String collectionToDelimitedString(Collection<?> coll, String delim, String prefix, String suffix) {
+		if (CollectionUtils.isEmpty(coll)) {
+			return "";
+		}
+		StringBuilder sb = new StringBuilder();
+		Iterator<?> it = coll.iterator();
+		while (it.hasNext()) {
+			sb.append(prefix).append(it.next()).append(suffix);
+			if (it.hasNext()) {
+				sb.append(delim);
+			}
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Convenience method to return a Collection as a delimited (e.g. CSV)
+	 * String. E.g. useful for {@code toString()} implementations.
+	 * @param coll the Collection to display
+	 * @param delim the delimiter to use (probably a ",")
+	 * @return the delimited String
+	 */
+	public static String collectionToDelimitedString(Collection<?> coll, String delim) {
+		return collectionToDelimitedString(coll, delim, "", "");
+	}
+
+	/**
+	 * Convenience method to return a Collection as a CSV String.
+	 * E.g. useful for {@code toString()} implementations.
+	 * @param coll the Collection to display
+	 * @return the delimited String
+	 */
+	public static String collectionToCommaDelimitedString(Collection<?> coll) {
+		return collectionToDelimitedString(coll, ",");
+	}
+
+	/**
+	 * Convenience method to return a String array as a delimited (e.g. CSV)
+	 * String. E.g. useful for {@code toString()} implementations.
+	 * @param arr the array to display
+	 * @param delim the delimiter to use (probably a ",")
+	 * @return the delimited String
+	 */
+	public static String arrayToDelimitedString(Object[] arr, String delim) {
+		if (ObjectUtils.isEmpty(arr)) {
+			return "";
+		}
+		if (arr.length == 1) {
+			return ObjectUtils.nullSafeToString(arr[0]);
+		}
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < arr.length; i++) {
+			if (i > 0) {
+				sb.append(delim);
+			}
+			sb.append(arr[i]);
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Convenience method to return a String array as a CSV String.
+	 * E.g. useful for {@code toString()} implementations.
+	 * @param arr the array to display
+	 * @return the delimited String
+	 */
+	public static String arrayToCommaDelimitedString(Object[] arr) {
+		return arrayToDelimitedString(arr, ",");
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/util/TypeReference.java b/reactor-core/src/main/java/reactor/util/TypeReference.java
new file mode 100644
index 0000000..8e093b0
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/TypeReference.java
@@ -0,0 +1,9 @@
+package reactor.util;
+
+/**
+ * Marker interface to provide generic type information.
+ *
+ * @author Jon Brisbin
+ */
+public interface TypeReference<T> {
+}
diff --git a/reactor-core/src/main/java/reactor/util/TypeUtils.java b/reactor-core/src/main/java/reactor/util/TypeUtils.java
new file mode 100644
index 0000000..3f43313
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/TypeUtils.java
@@ -0,0 +1,22 @@
+package reactor.util;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class TypeUtils {
+
+	protected TypeUtils() {
+	}
+
+	public static <T> Type fromGenericType(Class<T> type) {
+		return ((ParameterizedType)type.getGenericInterfaces()[0]).getActualTypeArguments()[0];
+	}
+
+	public static <T> Type fromTypeRef(TypeReference<T> typeRef) {
+		return fromGenericType(typeRef.getClass());
+	}
+
+}
diff --git a/reactor-core/src/main/java/reactor/util/UUIDUtils.java b/reactor-core/src/main/java/reactor/util/UUIDUtils.java
new file mode 100644
index 0000000..4d37304
--- /dev/null
+++ b/reactor-core/src/main/java/reactor/util/UUIDUtils.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.util;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Helper for creating random and Type 1 (time-based) UUIDs.
+ *
+ * @author Jon Brisbin
+ */
+public abstract class UUIDUtils {
+
+	private static boolean IS_THREADLOCALRANDOM_AVAILABLE = false;
+	private static       Random random;
+	private static final long   leastSigBits;
+	private static final ReentrantLock lock = new ReentrantLock();
+	private static long lastTime;
+
+	static {
+		try {
+			IS_THREADLOCALRANDOM_AVAILABLE = null != UUIDUtils.class.getClassLoader().loadClass(
+					"java.util.concurrent.ThreadLocalRandom"
+			);
+		} catch(ClassNotFoundException e) {
+		}
+
+		byte[] seed = new SecureRandom().generateSeed(8);
+		leastSigBits = new BigInteger(seed).longValue();
+		if(!IS_THREADLOCALRANDOM_AVAILABLE) {
+			random = new Random(leastSigBits);
+		}
+	}
+
+	private UUIDUtils() {
+	}
+
+	/**
+	 * Create a new random UUID.
+	 *
+	 * @return the new UUID
+	 */
+	public static UUID random() {
+		byte[] randomBytes = new byte[16];
+		if(IS_THREADLOCALRANDOM_AVAILABLE) {
+			java.util.concurrent.ThreadLocalRandom.current().nextBytes(randomBytes);
+		} else {
+			random.nextBytes(randomBytes);
+		}
+
+		long mostSigBits = 0;
+		for(int i = 0; i < 8; i++) {
+			mostSigBits = (mostSigBits << 8) | (randomBytes[i] & 0xff);
+		}
+		long leastSigBits = 0;
+		for(int i = 8; i < 16; i++) {
+			leastSigBits = (leastSigBits << 8) | (randomBytes[i] & 0xff);
+		}
+
+		return new UUID(mostSigBits, leastSigBits);
+	}
+
+	/**
+	 * Create a new time-based UUID.
+	 *
+	 * @return the new UUID
+	 */
+	public static UUID create() {
+		long timeMillis = (System.currentTimeMillis() * 10000) + 0x01B21DD213814000L;
+
+		lock.lock();
+		try {
+			if(timeMillis > lastTime) {
+				lastTime = timeMillis;
+			} else {
+				timeMillis = ++lastTime;
+			}
+		} finally {
+			lock.unlock();
+		}
+
+		// time low
+		long mostSigBits = timeMillis << 32;
+
+		// time mid
+		mostSigBits |= (timeMillis & 0xFFFF00000000L) >> 16;
+
+		// time hi and version
+		mostSigBits |= 0x1000 | ((timeMillis >> 48) & 0x0FFF); // version 1
+
+		return new UUID(mostSigBits, leastSigBits);
+	}
+
+}
diff --git a/reactor-core/src/main/resources/META-INF/reactor/default.properties b/reactor-core/src/main/resources/META-INF/reactor/default.properties
new file mode 100644
index 0000000..b28443a
--- /dev/null
+++ b/reactor-core/src/main/resources/META-INF/reactor/default.properties
@@ -0,0 +1,55 @@
+#
+# Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##
+# Dispatcher configuration
+#
+# Each dispatcher must be configured with a type:
+#
+# reactor.dispatchers.<name>.type = <type>
+#
+# Legal values for <type> are eventLoop, ringBuffer, synchronous, and threadPoolExecutor.
+
+# Depending on the type, further configuration is be possible:
+#
+# reactor.dispatchers.<name>.size:    eventLoop and threadPoolExecutor Dispatchers
+# reactor.dispatchers.<name>.backlog: eventLoop, ringBuffer, and threadPoolExecutor Dispatchers
+#
+# A size less than 1 may be specified to indicate that the size should be the same as the number
+# of CPUs.
+
+# A thread pool executor dispatcher, named threadPoolExecutor
+reactor.dispatchers.threadPoolExecutor.type = threadPoolExecutor
+reactor.dispatchers.threadPoolExecutor.size = 0
+# Backlog is how many Task objects to warm up internally
+reactor.dispatchers.threadPoolExecutor.backlog = 2048
+
+# An event loop dispatcher, named eventLoop
+reactor.dispatchers.eventLoop.type = eventLoop
+reactor.dispatchers.eventLoop.size = 0
+reactor.dispatchers.eventLoop.backlog = 2048
+
+# A ring buffer dispatcher, named ringBuffer
+reactor.dispatchers.ringBuffer.type = ringBuffer
+reactor.dispatchers.ringBuffer.backlog = 2048
+
+# A work queue dispatcher, named workQueue
+reactor.dispatchers.workQueue.type = workQueue
+reactor.dispatchers.workQueue.size = 0
+reactor.dispatchers.workQueue.backlog = 2048
+
+# The dispatcher named ringBuffer should be the default dispatcher
+reactor.dispatchers.default = ringBuffer
\ No newline at end of file
diff --git a/reactor-core/src/test/groovy/reactor/GroovyTestUtils.java b/reactor-core/src/test/groovy/reactor/GroovyTestUtils.java
new file mode 100644
index 0000000..ece6cfa
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/GroovyTestUtils.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor;
+
+import groovy.lang.Closure;
+import groovy.lang.GString;
+import reactor.event.Event;
+import reactor.function.*;
+import reactor.event.selector.ObjectSelector;
+import reactor.event.selector.Selector;
+
+/**
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public abstract class GroovyTestUtils {
+
+	protected GroovyTestUtils() {
+	}
+
+	public static Selector $(long l) {
+		return new ObjectSelector<Long>(l);
+	}
+
+	public static Selector $(String s) {
+		return new ObjectSelector<String>(s);
+	}
+
+	public static Selector $(GString s) {
+		return new ObjectSelector<String>(s.toString());
+	}
+
+	@SuppressWarnings({"rawtypes"})
+	public static Consumer<String> stringHandler(final Closure cl) {
+		return new Consumer<String>() {
+			public void accept(String s) {
+				cl.call(s);
+			}
+		};
+	}
+
+	@SuppressWarnings({"rawtypes"})
+	public static <T> Consumer consumer(final Closure cl) {
+		return new Consumer<T>() {
+			Class[] argTypes = cl.getParameterTypes();
+
+			@Override
+			@SuppressWarnings({"unchecked"})
+			public void accept(Object arg) {
+				if (argTypes.length < 1) {
+					cl.call();
+					return;
+				}
+				if (null != arg
+						&& argTypes[0] != Object.class
+						&& !argTypes[0].isAssignableFrom(arg.getClass())
+						&& arg instanceof Event) {
+					accept(((Event) arg).getData());
+					return;
+				}
+
+				cl.call(arg);
+			}
+		};
+	}
+
+	public static <K, V> Function<K, V> function(final Closure<V> cl) {
+		return new Function<K, V>() {
+			Class<?>[] argTypes = cl.getParameterTypes();
+
+			@Override
+			@SuppressWarnings({"unchecked"})
+			public V apply(K arg) {
+				if (argTypes.length < 1) {
+					return cl.call();
+				}
+				if (null != arg
+						&& argTypes[0] != Object.class
+						&& !argTypes[0].isAssignableFrom(arg.getClass())
+						&& arg instanceof Event) {
+					return apply((K) ((Event<?>) arg).getData());
+				}
+
+				return cl.call(arg);
+			}
+		};
+	}
+	public static <K> Predicate<K> predicate(final Closure<Boolean> cl) {
+		return new Predicate<K>() {
+			Class<?>[] argTypes = cl.getParameterTypes();
+
+			@SuppressWarnings("unchecked")
+			@Override
+			public boolean test(K arg) {
+				if (argTypes.length < 1) {
+					return cl.call();
+				}
+				if (null != arg
+						&& argTypes[0] != Object.class
+						&& !argTypes[0].isAssignableFrom(arg.getClass())
+						&& arg instanceof Event) {
+					return test((K) ((Event<?>) arg).getData());
+				}
+
+				return cl.call(arg);
+			}
+		};
+	}
+
+	public static <V> Supplier<V> supplier(final Closure<V> cl) {
+		return new Supplier<V>() {
+
+			@Override
+			public V get() {
+				return cl.call();
+			}
+		};
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/alloc/AllocatorsSpec.groovy b/reactor-core/src/test/groovy/reactor/alloc/AllocatorsSpec.groovy
new file mode 100644
index 0000000..7851dbe
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/alloc/AllocatorsSpec.groovy
@@ -0,0 +1,107 @@
+package reactor.alloc
+
+import reactor.event.Event
+import reactor.function.Supplier
+import reactor.util.TypeReference
+import reactor.util.TypeUtils
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Jon Brisbin
+ */
+class AllocatorsSpec extends Specification {
+
+	def "a ReferenceCountingAllocator should properly count references"() {
+
+		given: "references and a thread pool"
+			def threadPool = Executors.newCachedThreadPool()
+			def pool = new ReferenceCountingAllocator(4, {
+				new Recyclable() {
+					@Override
+					void recycle() {
+					}
+				}
+			} as Supplier<Object>)
+			def refs = (1..4).collect {
+				pool.allocate()
+			}
+			def latch = new CountDownLatch(refs.size())
+
+		when: "references are retained from other threads"
+			refs.each { ref ->
+				threadPool.submit(new ReferenceCounter(latch, ref, 1))
+			}
+			latch.await(5, TimeUnit.SECONDS)
+
+		then: "references were all retained"
+			refs.findAll { ref -> ref.referenceCount != 2 }.isEmpty()
+
+		when: "references are released"
+			latch = new CountDownLatch(refs.size())
+			refs.each { ref ->
+				threadPool.submit(new ReferenceCounter(latch, ref, -2))
+			}
+			latch.await(5, TimeUnit.SECONDS)
+
+		then: "references were all released"
+			refs.findAll { ref -> ref.referenceCount != 0 }.isEmpty()
+
+		when: "the allocator pool is expanded"
+			refs = (1..8).collect { pool.allocate() }
+			def refToFind = refs[0]
+
+		then: "the pool was expanded"
+			refs.findAll { ref -> ref.referenceCount == 1 }.size() == 8
+
+		and: "the references are all unique"
+			refs.findAll { ref -> ref == refToFind }.size() == 1
+
+	}
+
+	def "Allocators can be provided by Type"() {
+
+		given: "a generic type"
+			def type = TypeUtils.fromTypeRef(new TypeReference<Event<String>>() {})
+			def allocators = [:]
+			allocators[type] = new ReferenceCountingAllocator<Event<String>>(new Supplier<Event<String>>() {
+				@Override
+				Event<String> get() {
+					return Event.wrap("Hello World!")
+				}
+			})
+
+		when: "a pool is requested"
+			def pool = allocators[TypeUtils.fromTypeRef(new TypeReference<Event<String>>() {})]
+
+		then: "a pool was returned"
+			pool
+
+	}
+
+	class ReferenceCounter implements Runnable {
+		CountDownLatch latch
+		Reference ref
+		int delta
+
+		ReferenceCounter(CountDownLatch latch, Reference ref, int delta) {
+			this.latch = latch
+			this.ref = ref
+			this.delta = delta;
+		}
+
+		@Override
+		void run() {
+			if (delta > 0) {
+				ref.retain(delta)
+			} else {
+				ref.release(Math.abs(delta))
+			}
+			latch.countDown()
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/alloc/FactoryAllocatorSpec.groovy b/reactor-core/src/test/groovy/reactor/alloc/FactoryAllocatorSpec.groovy
new file mode 100644
index 0000000..e885676
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/alloc/FactoryAllocatorSpec.groovy
@@ -0,0 +1,41 @@
+package reactor.alloc
+
+import reactor.alloc.factory.Factories
+import spock.lang.Specification
+
+/**
+ * @author Jon Brisbin
+ */
+class FactoryAllocatorSpec extends Specification {
+
+	def "factory can provide objects from a delegate"() {
+
+		given: "a factory for objects and an allocator"
+			def pool = Factories.create(Pojo)
+
+		when: "items are requested from the pool"
+			def items = []
+			(1..512).each { i ->
+				items << pool.get()
+			}
+
+		then: "the pool was filled with unique items"
+			items.size() == 512
+			items.findAll { it == null }.isEmpty()
+			items.findAll { it.equals(items[0]) }.size() == 1
+
+		when: "more items are requested from the pool than were originally created"
+			(1..1024).each { i ->
+				items << pool.get()
+			}
+
+		then: "more unique items were created internally"
+			items.size() == 1536
+			items.findAll { it == null }.isEmpty()
+			items.findAll { it.equals(items[0]) }.size() == 1
+
+	}
+
+	static class Pojo {}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/BoundarySpec.groovy b/reactor-core/src/test/groovy/reactor/core/BoundarySpec.groovy
new file mode 100644
index 0000000..7af514f
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/BoundarySpec.groovy
@@ -0,0 +1,45 @@
+package reactor.core
+
+import reactor.core.spec.Reactors
+import reactor.event.Event
+import reactor.function.Consumer
+import reactor.function.support.Boundary
+import spock.lang.Ignore
+import spock.lang.Specification
+
+import java.util.concurrent.TimeUnit
+
+import static reactor.GroovyTestUtils.$
+
+/**
+ * @author Jon Brisbin
+ */
+class BoundarySpec extends Specification {
+
+	//@Ignore
+	def "A Boundary can wait on multiple Consumers"() {
+
+		given: "A Boundary with multiple Consumers bound to it"
+		def r = Reactors.reactor().
+				env(new Environment()).
+				dispatcher(Environment.THREAD_POOL).
+				get()
+		def boundary = new Boundary()
+		def hellos = Collections.synchronizedList([])
+		(1..5).each {
+			r.on($("test.$it"), boundary.bind({ Event<String> ev ->
+				hellos << ev.data
+			} as Consumer<Event<String>>))
+		}
+
+		when: "Consumers are triggered"
+		(1..5).each {
+			r.notify("test.$it".toString(), Event.wrap("Hello World!"))
+		}
+
+		then: "Boundary should block until all 5 have counted down"
+		boundary.await(5, TimeUnit.SECONDS)
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/EnvironmentSpec.groovy b/reactor-core/src/test/groovy/reactor/core/EnvironmentSpec.groovy
new file mode 100644
index 0000000..9507b8f
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/EnvironmentSpec.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core
+
+import reactor.core.configuration.ConfigurationReader
+import reactor.core.configuration.ReactorConfiguration
+import reactor.event.dispatch.Dispatcher
+import spock.lang.Specification
+
+class EnvironmentSpec extends Specification {
+
+  def "An environment cleans up its Dispatchers when it's shut down"() {
+
+    given:
+      "An Environment"
+
+      ReactorConfiguration configuration = new ReactorConfiguration([], 'default', [:] as Properties)
+      Dispatcher dispatcher = Mock(Dispatcher)
+      Environment environment = new Environment(['alpha': [dispatcher, dispatcher], 'bravo': [dispatcher]], Mock(ConfigurationReader, {
+        read() >> configuration
+      }))
+
+    when:
+      "it is shut down"
+      environment.shutdown()
+
+    then:
+      "its dispatchers are cleaned up"
+      3 * dispatcher.shutdown()
+  }
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/PollerSpec.groovy b/reactor-core/src/test/groovy/reactor/core/PollerSpec.groovy
new file mode 100644
index 0000000..49c8d89
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/PollerSpec.groovy
@@ -0,0 +1,74 @@
+package reactor.core
+
+import reactor.function.Consumer
+import reactor.function.Supplier
+import reactor.function.Suppliers
+import reactor.function.support.Poller
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Jon Brisbin
+ */
+class PollerSpec extends Specification {
+
+	def 'Poller connects a Supplier and a Consumer'() {
+
+		given:
+			'a Supplier and a Consumer'
+			def latch = new CountDownLatch(1)
+			def hw = ""
+			def supplier = { return "Hello World!" } as Supplier<String>
+			def consumer = { String s -> hw = s; latch.countDown() } as Consumer<String>
+
+		when:
+			'a Poller is created between the two'
+			def poller = new Poller(supplier, consumer)
+
+		then:
+			'the Supplier has notified the Consumer'
+			latch.await(1, TimeUnit.SECONDS)
+			hw == "Hello World!"
+
+		cleanup:
+			poller.shutdown()
+
+	}
+
+	def 'Poller aggregates multiple Suppliers into a single Consumer'() {
+
+		given:
+			'multiple Suppliers'
+			def latch = new CountDownLatch(5)
+			def suppliers = [
+					Suppliers.supplyOnce(1),
+					Suppliers.supplyOnce(2),
+					Suppliers.supplyOnce(3),
+					Suppliers.supplyOnce(4),
+					Suppliers.supplyOnce(5)
+			]
+			def aggregate = Suppliers.collect(suppliers)
+			def sum = 0
+			def consumer = { Integer i ->
+				println "i=$i"
+				sum += i
+				latch.countDown()
+			} as Consumer<Integer>
+
+		when:
+			'a Poller is created between the two'
+			def poller = new Poller(aggregate, consumer)
+
+		then:
+			'the Supplier has notified the Consumer'
+			latch.await(1, TimeUnit.SECONDS)
+			sum == 15
+
+		cleanup:
+			poller.shutdown()
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/ReactorsSpec.groovy b/reactor-core/src/test/groovy/reactor/core/ReactorsSpec.groovy
new file mode 100644
index 0000000..5f4ba81
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/ReactorsSpec.groovy
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package reactor.core
+
+import reactor.core.spec.Reactors
+import reactor.event.Event
+import reactor.event.dispatch.SynchronousDispatcher
+import reactor.event.routing.ConsumerFilteringEventRouter
+import reactor.filter.RoundRobinFilter
+import reactor.function.Consumer
+import reactor.function.Functions
+import reactor.function.support.SingleUseConsumer
+import reactor.tuple.Tuple2
+import spock.lang.Shared
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import static reactor.GroovyTestUtils.*
+import static reactor.event.selector.Selectors.*
+
+/**
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+class ReactorsSpec extends Specification {
+
+	@Shared
+	Environment testEnv
+
+	void setupSpec() {
+		testEnv = new Environment()
+	}
+
+	def "A Reactor can be configured with Reactors.reactor()"() {
+
+		when:
+			"Building a Synchronous Reactor"
+			def reactor = Reactors.reactor().synchronousDispatcher().get()
+
+		then:
+			"Dispatcher has been set to Synchronous"
+			reactor.dispatcher instanceof SynchronousDispatcher
+
+		when:
+			"Building a RoundRobin Reactor"
+			reactor = Reactors.reactor().roundRobinEventRouting().get()
+
+		then:
+			"EventRouter has been correctly set"
+			reactor.eventRouter instanceof ConsumerFilteringEventRouter
+			((ConsumerFilteringEventRouter) reactor.eventRouter).filter instanceof RoundRobinFilter
+	}
+
+	def "A Reactor can dispatch events properly"() {
+
+		given:
+			"a plain Reactor and a simple consumer on \$('test')"
+			def reactor = Reactors.reactor().synchronousDispatcher().get()
+			def data = ""
+			Thread t = null
+			reactor.on($("test"), { ev ->
+				data = ev.data
+				t = Thread.currentThread()
+			} as Consumer<Event<String>>
+			)
+
+		when:
+			"Reactor is notified on 'test'"
+			reactor.notify("test", Event.wrap("Hello World!"))
+
+		then:
+			"Data and T have been populated"
+			data == "Hello World!"
+			Thread.currentThread() == t
+
+		when:
+			"Reactor is notified on 'test' and completion callback is attached"
+			def completion = ""
+			reactor.notify("test", Event.wrap("Hello World!"), consumer { completion = it.data })
+
+		then:
+			"Completion callback has been called"
+			completion == "Hello World!"
+
+		when:
+			"Reactor is notified on 'test' with a Supplier"
+			data = ""
+			reactor.notify("test", supplier { Event.wrap("Hello World!") })
+
+		then:
+			"Completion callback has been called"
+			data == "Hello World!"
+
+		when:
+			"Reactor is notified without event"
+			data = "something"
+			reactor.notify('test')
+
+		then:
+			"Null event has been passed to the consumer"
+			!data
+	}
+
+	def "A Registration is pausable and cancellable"() {
+
+		given:
+			"a simple reactor implementation"
+			def reactor = Reactors.reactor().synchronousDispatcher().get()
+			def data = ""
+			def reg = reactor.on($("test"), { ev ->
+				data = ev.data
+			} as Consumer<Event<String>>)
+			reg.pause()
+
+		when:
+			"event is triggered"
+			reactor.notify("test", Event.wrap("Hello World!"))
+
+		then:
+			"data should not have updated"
+			data == ""
+
+		when:
+			"registration is cancelled"
+			reg.cancel()
+			reactor.notify("test", Event.wrap("Hello World!"))
+
+		then:
+			"it shouldn't be found any more"
+			data == ""
+			!reactor.respondsToKey("test")
+
+	}
+
+	def "A Reactor can reply to events"() {
+
+		def r = Reactors.reactor().synchronousDispatcher().get()
+
+		given:
+			"a simple consumer"
+			def data = ""
+			def a = { ev ->
+				data = ev.data
+			}
+
+		when:
+			"an event is dispatched to the global reactor"
+			r.on($('say-hello'), a as Consumer<Event<String>>)
+			r.notify('say-hello', new Event<String>('Hello World!'))
+
+		then:
+			"the data is updated"
+			data == 'Hello World!'
+
+	}
+
+	def "A Reactor can support send and receive"() {
+
+		given:
+			"a simple Reactor and a response-producing Function"
+			def r = Reactors.reactor().synchronousDispatcher().get()
+			def f = function { s ->
+				"Hello World!"
+			}
+
+			def sel = $("hello")
+			def replyTo = $()
+
+			def result = ""
+			r.on(replyTo, { ev ->
+				result = ev.data
+			} as Consumer<Event<String>>)
+
+		when:
+			"a Function is assigned that produces a Response"
+			r.receive(sel, f)
+
+		and:
+			"an Event is triggered"
+			r.send("hello", Event.wrap("Hello World!", replyTo.object))
+
+		then:
+			"the result should have been updated"
+			result == "Hello World!"
+
+		when:
+			"an event is triggered through Supplier#get()"
+			result = ""
+			r.send("hello", supplier { Event.wrap("Hello World!", replyTo.object) })
+
+		then:
+			"the result should have been updated"
+			result == "Hello World!"
+
+		when:
+			"A different reactor is provided"
+			def r2 = Reactors.reactor().get()
+
+		and:
+			"A new consumer is attached"
+			r2.on(replyTo, consumer {
+				result = it.data
+			})
+
+		and:
+			"an event is triggered using the provided reactor"
+			result = ""
+			r.send("hello", Event.wrap("Hello World!", replyTo.object), r2)
+
+		then:
+			"the result should have been updated"
+			result == "Hello World!"
+
+		when:
+			"an event is triggered using the provided reactor through Supplier#get()"
+			result = ""
+			r.send("hello", supplier { Event.wrap("Hello World!", replyTo.object) }, r2)
+
+		then:
+			"the result should have been updated"
+			result == "Hello World!"
+
+		when:
+			"a registered function returns null"
+			r.receive($('test3'), function { s ->
+				null
+			})
+
+		and:
+			"a replyTo consumer listens"
+			result = 'something'
+			r.on($('testReply3'), consumer { result = it.data })
+
+		and:
+			"send on 'test3'"
+			r.send 'test3', Event.wrap('anything', 'testReply3')
+
+		then:
+			"result should be null"
+			!result
+
+
+		when:
+			"a registered function rises exception"
+			r.receive($('test4'), function { s ->
+				throw new Exception()
+			})
+
+		and:
+			"a replyTo consumer listens"
+			result = 'something'
+			r.on($('testReply4'), consumer { result = it.data })
+
+		and:
+			"a T(Exception) consumer listens"
+			def e
+			r.on(T(Exception), consumer { e = it } as Consumer<Exception>)
+
+		and:
+			"send on 'test4'"
+			r.send 'test4', Event.wrap('anything', 'testReply4')
+
+		then:
+			"result should not be null and exception called"
+			result
+			e
+	}
+
+	def "A Consumer can be unassigned"() {
+
+		given:
+			"a normal reactor"
+			def reactor = Reactors.reactor().synchronousDispatcher().get()
+
+		when:
+			"registering few handlers"
+			reactor.on R('t[a-z]st'), Functions.consumer { println 'test1' }
+			reactor.on R('t[a-z]st'), Functions.consumer { println 'test2' }
+
+			reactor.notify "test", Event.wrap("test")
+
+		then:
+			"will report false when asked whether it responds to an unmatched key"
+			reactor.respondsToKey 'test'
+			reactor.consumerRegistry.unregister('test')
+			!reactor.respondsToKey('test')
+	}
+
+	def "Multiple consumers can use the same selector"() {
+
+		given:
+			"a normal synchronous reactor"
+			def r = Reactors.reactor().synchronousDispatcher().get()
+			def d1, d2
+			def selector = $("test")
+
+		when:
+			"registering two consumers on the same selector"
+			r.on(selector, consumer { d1 = true })
+			r.on(selector, consumer { d2 = true })
+
+		then:
+			"both consumers are notified"
+			r.notify 'test', new Event('foo')
+			d1 && d2
+	}
+
+	def "A Reactor can support single-use Consumers"() {
+
+		given:
+			"a synchronous Reactor and a single-use Consumer"
+			def r = Reactors.reactor().get()
+			def count = 0
+			r.on($('test'),new SingleUseConsumer(consumer { count++ }))
+
+		when:
+			"the consumer is invoked several times"
+			r.notify('test',Event.wrap(null))
+			r.notify('test',Event.wrap(null))
+
+		then:
+			"the count is only 1"
+			count == 1
+
+	}
+
+	def "Environment provides a root reactor"() {
+
+		given:
+			"a root Reactor from environment and a simple Consumer"
+			def r = testEnv.rootReactor
+			def latch = new CountDownLatch(1)
+			r.on($('test'),consumer { latch.countDown() })
+
+		when:
+			"the consumer is invoked"
+			r.notify('test',Event.wrap(null))
+
+		then:
+			"the consumer has been triggered"
+			latch.await(3, TimeUnit.SECONDS)
+
+	}
+
+	def "A Reactor can register consumer for errors"() {
+
+		given:
+			"a synchronous Reactor"
+			def r = Reactors.reactor().synchronousDispatcher().get()
+
+		when:
+			"an error consumer is registered"
+			def latch = new CountDownLatch(1)
+			def e = null
+			r.on(T(Exception),
+					consumer { Exception ex -> e = ex; latch.countDown() }
+					as Consumer<Exception>
+			)
+
+		and:
+			"a normal consumer that rise exceptions"
+			r.on($('test'),consumer { throw new Exception('bad') })
+
+		and:
+			"the consumer is invoked"
+			r.notify('test',Event.wrap(null))
+
+		then:
+			"consumer has been invoked and e is an exception"
+			latch.await(3, TimeUnit.SECONDS)
+			e && e instanceof Exception
+
+	}
+
+	def "A Producer can override consumer for errors"() {
+
+		given:
+			"a synchronous Reactor"
+			def r = Reactors.reactor().synchronousDispatcher().get()
+
+		when:
+			"an error consumer is registered"
+			def latch = new CountDownLatch(1)
+			def e = null
+			def x
+			r.on(T(Exception),
+					consumer { x = false }
+					as Consumer<Exception>
+			)
+
+		and:
+			"a normal consumer that rise exceptions"
+			r.on($('test'),consumer { throw new Exception('bad') })
+
+		and:
+			"the consumer is invoked with an error consumer bound event"
+			r.notify('test',new Event(null, null, consumer { Exception ex -> e = ex; latch.countDown() }))
+
+		then:
+			"consumer has been invoked and e is an exception but x is not set"
+			latch.await(3, TimeUnit.SECONDS)
+			e && e instanceof Exception
+			!x
+
+	}
+
+	def "A Reactor can run arbitrary consumer"() {
+
+		given:
+			"a synchronous Reactor"
+			def r = Reactors.reactor().synchronousDispatcher().get()
+
+		when:
+			"the Reactor default Selector is notified with a tuple of consumer and data"
+			def latch = new CountDownLatch(1)
+			def e = null
+			r.schedule(consumer { e = it; latch.countDown() }, 'test')
+
+
+		then:
+			"consumer has been invoked and e is 'test'"
+			latch.await(3, TimeUnit.SECONDS)
+			e == 'test'
+
+		when:
+			"a consumer listen for failures"
+			latch = new CountDownLatch(1)
+			r.on(T(Exception),
+					consumer { latch.countDown() }
+					as Consumer<Exception>)
+
+		and:
+			"the arbitrary consumer fails"
+			latch = new CountDownLatch(1)
+			r.schedule(consumer { throw new Exception() }, 'test')
+
+
+		then:
+			"error consumer has been invoked"
+			latch.await(3, TimeUnit.SECONDS)
+	}
+
+	def "A Reactor can handle errors"() {
+
+		given:
+			"a synchronous Reactor with a dispatch error handler"
+			def count = 0
+			def r = Reactors.reactor().
+					synchronousDispatcher().
+					dispatchErrorHandler(consumer { t -> count++ }).
+					uncaughtErrorHandler(consumer { t -> count++ }).
+					get()
+			r.on $('test'), consumer { ev -> throw new Exception("error") }
+
+		when:
+			"an error is produced"
+			r.notify('test', Event.wrap(null))
+
+		then:
+			"the count has increased"
+			count == 1
+
+		when:
+			"notify an error"
+			r.notify('test', Event.wrap(new IllegalStateException("error")))
+
+		then:
+			"the count has increased"
+			count == 2
+
+	}
+
+}
+
diff --git a/reactor-core/src/test/groovy/reactor/core/ResequencersSpec.groovy b/reactor-core/src/test/groovy/reactor/core/ResequencersSpec.groovy
new file mode 100644
index 0000000..51cac76
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/ResequencersSpec.groovy
@@ -0,0 +1,35 @@
+package reactor.core
+
+import reactor.function.Consumer
+import reactor.function.support.Resequencer
+import spock.lang.Specification
+
+/**
+ * @author Jon Brisbin
+ */
+class ResequencersSpec extends Specification {
+
+	def "re-orders events in the proper order"() {
+
+		given:
+			"a Resequencer"
+			def nums = []
+			def outOfOrderNums = [2, 3, 1, 5, 4] as Long[]
+			def seq = new Resequencer<Long>({ l -> nums << l } as Consumer<Long>)
+
+		when:
+			"slots are allocated and claimed"
+			(1..5).each {
+				seq.next()
+			}
+			outOfOrderNums.each {
+				seq.accept(it, it)
+			}
+
+		then:
+			"slots were re-ordered"
+			nums == outOfOrderNums.sort()
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/composable/spec/PromisesSpec.groovy b/reactor-core/src/test/groovy/reactor/core/composable/spec/PromisesSpec.groovy
new file mode 100644
index 0000000..c6d783c
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/composable/spec/PromisesSpec.groovy
@@ -0,0 +1,812 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.composable.spec
+
+import reactor.core.Environment
+import reactor.core.Observable
+import reactor.core.composable.Deferred
+import reactor.core.composable.Promise
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import static reactor.GroovyTestUtils.*
+
+/**
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ * @author Jon Brisbin
+ */
+class PromisesSpec extends Specification {
+
+	def "An onComplete consumer is called when a promise is rejected"() {
+		given:
+			"a Promise with an onComplete Consumer"
+			def deferred = Promises.<Object> defer().get()
+			def promise = deferred.compose()
+			def acceptedPromise
+
+			promise.onComplete(consumer { self ->
+				acceptedPromise = self
+			})
+
+		when:
+			"the promise is rejected"
+			deferred.accept new Exception()
+
+		then:
+			"the consumer is invoked with the promise"
+			acceptedPromise == promise
+			promise.error
+	}
+
+	def "An onComplete consumer is called when added to an already-rejected promise"() {
+		given:
+			"a rejected Promise"
+			def promise = Promises.<Object> error(new Exception()).get()
+
+		when:
+			"an onComplete consumer is added"
+			def acceptedPromise
+
+			promise.onComplete(consumer { self ->
+				acceptedPromise = self
+			})
+
+		then:
+			"the consumer is invoked with the promise"
+			acceptedPromise == promise
+			promise.error
+	}
+
+	def "An onComplete consumer is called when a promise is fulfilled"() {
+		given:
+			"a Promise with an onComplete Consumer"
+			def deferred = Promises.<Object> defer().get()
+			def promise = deferred.compose()
+			def acceptedPromise
+
+			promise.onComplete(consumer { self ->
+				acceptedPromise = self
+			})
+
+		when:
+			"the promise is fulfilled"
+			deferred.accept 'test'
+
+		then:
+			"the consumer is invoked with the promise"
+			acceptedPromise == promise
+			promise.success
+	}
+
+	def "An onComplete consumer is called when added to an already-fulfilled promise"() {
+		given:
+			"a fulfilled Promise"
+			def promise = Promises.success('test').get()
+
+		when:
+			"an onComplete consumer is added"
+			def acceptedPromise
+
+			promise.onComplete(consumer { self ->
+				acceptedPromise = self
+			})
+
+		then:
+			"the consumer is invoked with the promise"
+			acceptedPromise == promise
+			promise.success
+	}
+
+	def "An onSuccess consumer is called when a promise is fulfilled"() {
+		given:
+			"a Promise with an onSuccess Consumer"
+			def deferred = Promises.<Object> defer().get()
+			def promise = deferred.compose()
+			def acceptedValue
+
+			promise.onSuccess(consumer { v ->
+				acceptedValue = v
+			})
+
+		when:
+			"the promise is fulfilled"
+			deferred.accept 'test'
+
+		then:
+			"the consumer is invoked with the fulfilling value"
+			acceptedValue == 'test'
+	}
+
+	def "An onSuccess consumer is called when added to an already-fulfilled promise"() {
+		given:
+			"a fulfilled Promise"
+			def promise = Promises.success('test').get()
+
+		when:
+			"an onSuccess consumer is added"
+			def acceptedValue
+
+			promise.onSuccess(consumer { v ->
+				acceptedValue = v
+			})
+
+		then:
+			"the consumer is invoked with the fulfilling value"
+			acceptedValue == 'test'
+	}
+
+	def "An onSuccess consumer can be added to an already-rejected promise"() {
+		given:
+			"a rejected Promise"
+			def promise = Promises.error(new Exception()).get()
+
+		when:
+			"an onSuccess consumer is added"
+			promise.onSuccess(consumer {})
+
+		then:
+			"no exception is thrown"
+			notThrown Exception
+	}
+
+	def "An onError consumer can be added to an already-fulfilled promise"() {
+		given:
+			"a fulfilled Promise"
+			def promise = Promises.success('test').get()
+
+		when:
+			"an onError consumer is added"
+			promise.onSuccess(consumer {})
+
+		then:
+			"no exception is thrown"
+			notThrown Exception
+	}
+
+	def "An onError consumer is called when a promise is rejected"() {
+		given:
+			"a Promise with an onError Consumer"
+			def deferred = Promises.<Object> defer().get()
+			def promise = deferred.compose()
+			def acceptedValue
+
+			promise.onError(consumer { v ->
+				acceptedValue = v
+			})
+
+		when:
+			"the promise is rejected"
+			def failure = new Exception()
+			deferred.accept failure
+
+		then:
+			"the consumer is invoked with the rejecting value"
+			acceptedValue == failure
+	}
+
+	def "An onError consumer is called when added to an already-rejected promise"() {
+		given:
+			"a rejected Promise"
+			def failure = new Exception()
+			def promise = Promises.error(failure).get()
+
+		when:
+			"an onError consumer is added"
+			def acceptedValue
+
+			promise.onError(consumer { v ->
+				acceptedValue = v
+			})
+
+		then:
+			"the consumer is invoked with the rejecting value"
+			acceptedValue == failure
+	}
+
+	def "When getting a rejected promise's value the exception that the promise was rejected with is thrown"() {
+		given:
+			"a rejected Promise"
+			def failure = new Exception()
+			def promise = Promises.error(failure).get()
+
+		when:
+			"getting the promise's value"
+			promise.get()
+
+		then:
+			"the error that the promise was rejected with is thrown"
+			thrown(Exception)
+	}
+
+	def "A fulfilled promise's value is returned by get"() {
+		given:
+			"a fulfilled Promise"
+			def promise = Promises.success('test').get()
+
+		when:
+			"getting the promise's value"
+			def value = promise.get()
+
+		then:
+			"the value used to fulfil the promise is returned"
+			value == 'test'
+	}
+
+	def "A promise can be fulfilled with null"() {
+		given:
+			"a promise"
+			def deferred = Promises.<Object> defer().get()
+			def promise = deferred.compose()
+
+		when:
+			"the promise is fulfilled with null"
+			deferred.accept null
+
+		then:
+			"the promise was successful"
+			promise.isComplete()
+			promise.isSuccess()
+	}
+
+	def "An Observable can be used to consume a promise's value when it's fulfilled"() {
+		given:
+			"a promise with a consuming Observable"
+			def deferred = Promises.<Object> defer().get()
+			def promise = deferred.compose()
+			def observable = Mock(Observable)
+			promise.consume('key', observable)
+
+		when:
+			"the promise is fulfilled"
+			deferred.accept 'test'
+
+		then:
+			"the observable is notified"
+			1 * observable.notify('key', { event -> event.data == 'test' })
+	}
+
+	def "An Observable can be used to consume the value of an already-fulfilled promise"() {
+		given:
+			"a fulfilled promise"
+			def promise = Promises.success('test').get()
+			def observable = Mock(Observable)
+
+		when:
+			"an Observable is added as a consumer"
+			promise.consume('key', observable)
+
+		then:
+			"the observable is notified"
+			1 * observable.notify('key', { event -> event.data == 'test' })
+	}
+
+	def "A function can be used to map a Promise's value when it's fulfilled"() {
+		given:
+			"a promise with a mapping function"
+			def deferred = Promises.<Integer> defer().get()
+			def promise = deferred.compose()
+			def mappedPromise = promise.map(function { it * 2 })
+
+		when:
+			"the original promise is fulfilled"
+			deferred.accept 1
+
+		then:
+			"the mapped promise is fulfilled with the mapped value"
+			mappedPromise.get() == 2
+	}
+
+	def "A map many can be used to bind to another Promise and compose asynchronous results "() {
+		given:
+			"a promise with a map many function"
+			def deferred = Promises.<Integer> defer().get()
+			def promise = deferred.compose()
+			def mappedPromise = promise.mapMany(function { Promises.success(it + 1).get() })
+
+		when:
+			"the original promise is fulfilled"
+			deferred.accept 1
+
+		then:
+			"the mapped promise is fulfilled with the mapped value"
+			mappedPromise.get() == 2
+	}
+
+	def "A function can be used to map an already-fulfilled Promise's value"() {
+		given:
+			"a fulfilled promise with a mapping function"
+			def promise = Promises.success(1).get()
+
+		when:
+			"a mapping function is added"
+			def mappedPromise = promise.map(function { it * 2 })
+
+		then:
+			"the mapped promise is fulfilled with the mapped value"
+			mappedPromise.get() == 2
+	}
+
+	def "An onSuccess consumer registered via then is called when the promise is fulfilled"() {
+		given:
+			"A promise with an onSuccess consumer registered using then"
+			Deferred<String, Promise<String>> deferred = Promises.<String> defer().get()
+			Promise<String> promise = deferred.compose()
+			def value = null
+			promise.then(consumer { value = it }, null)
+
+		when:
+			"The promise is fulfilled"
+			deferred.accept 'test'
+
+		then:
+			"the consumer is called"
+			value == 'test'
+	}
+
+	def "An onError consumer registered via then is called when the promise is rejected"() {
+		given:
+			"A promise with an onError consumer registered using then"
+			Deferred<String> promise = Promises.<String> defer().synchronousDispatcher().get()
+			def value
+			promise.compose().then(consumer {}, consumer { value = it })
+
+		when:
+			"The promise is rejected"
+			def e = new Exception()
+			promise.accept e
+
+		then:
+			"the consumer is called"
+			value == e
+	}
+
+	def "An onError consumer registered via then is called when the promise is already rejected"() {
+		given:
+			"A promise that has been rejected"
+			def e = new Exception()
+			def promise = Promises.<String> error(e).synchronousDispatcher().get()
+
+		when:
+			"An onError consumer is registered via then"
+			def value
+			promise.then(consumer {}, consumer { value = it })
+
+		then:
+			"The consumer is called"
+			value == e
+	}
+
+	def "An onSuccess consumer registered via then is called when the promise is already fulfilled"() {
+		given:
+			"A promise that has been fulfilled"
+			def promise = Promises.success('test').synchronousDispatcher().get()
+
+		when:
+			"An onSuccess consumer is registered via then"
+			def value
+			promise.then(consumer { value = it }, null)
+
+		then:
+			"The consumer is called"
+			value == 'test'
+	}
+
+	def "An onSuccess function registered via then is called when the promise is fulfilled"() {
+		given:
+			"a promise with an onSuccess function registered using then"
+			Deferred<String> promise = Promises.<String> defer().synchronousDispatcher().get()
+			def transformed = promise.compose().then(function { it * 2 }, null)
+
+		when:
+			"the promise is fulfilled"
+			promise.accept 1
+
+		then:
+			"the function is called and the transformed promise is fulfilled"
+			transformed.get() == 2
+	}
+
+	def "An onSuccess function registered via then is called when the promise is already fulfilled"() {
+		given:
+			"A promise that has been fulfilled"
+			def promise = Promises.success(1).synchronousDispatcher().get()
+
+		when:
+			"An onSuccess function is registered via then"
+			def transformed = promise.then(function { it * 2 }, null)
+
+		then:
+			"The function is called and the transformed promise is fulfilled"
+			transformed.success
+			transformed.get() == 2
+	}
+
+	def "When a promise is fulfilled, if a mapping function throws an exception the mapped promise is rejected"() {
+		given:
+			"a promise with a filter that throws an exception"
+			Deferred<String> promise = Promises.<String> defer().synchronousDispatcher().get()
+			def e = new RuntimeException()
+			def mapped = promise.compose().map(function { throw e })
+
+		when:
+			"the promise is fulfilled"
+			promise.accept 2
+
+		then:
+			"the mapped promise is rejected"
+			mapped.error
+	}
+
+	def "When a promise is already fulfilled, if a mapping function throws an exception the mapped promise is rejected"() {
+		given:
+			"a fulfilled promise"
+			def promise = Promises.success(1).synchronousDispatcher().get()
+
+		when:
+			"a mapping function that throws an exception is added"
+			def e = new RuntimeException()
+			def mapped = promise.map(function { throw e })
+
+		then:
+			"the mapped promise is rejected"
+			mapped.error
+	}
+
+	def "An IllegalStateException is thrown if an attempt is made to fulfil a fulfilled promise"() {
+		given:
+			"a fulfilled promise"
+			def promise = Promises.defer().synchronousDispatcher().get()
+
+		when:
+			"an attempt is made to fulfil it"
+			promise.accept 1
+			promise.accept 1
+
+		then:
+			"an IllegalStateException is thrown"
+			thrown(IllegalStateException)
+	}
+
+	def "An IllegalStateException is thrown if an attempt is made to reject a rejected promise"() {
+		given:
+			"a rejected promise"
+			Deferred promise = Promises.defer().synchronousDispatcher().get()
+
+		when:
+			"an attempt is made to fulfil it"
+			promise.accept new Exception()
+			promise.accept new Exception()
+
+		then:
+			"an IllegalStateException is thrown"
+			thrown(IllegalStateException)
+	}
+
+	def "An IllegalStateException is thrown if an attempt is made to reject a fulfilled promise"() {
+		given:
+			"a fulfilled promise"
+			def promise = Promises.defer().synchronousDispatcher().get()
+
+		when:
+			"an attempt is made to fulfil it"
+			promise.accept 1
+			promise.accept new Exception()
+
+		then:
+			"an IllegalStateException is thrown"
+			thrown(IllegalStateException)
+	}
+
+	def "Multiple promises can be combined"() {
+		given:
+			"two fulfilled promises"
+			Deferred promise1 = Promises.defer().synchronousDispatcher().get()
+			Deferred promise2 = Promises.defer().synchronousDispatcher().get()
+
+		when:
+			"a combined promise is first created"
+			Promise combined = Promises.when(promise1, promise2)
+
+		then:
+			"it is pending"
+			combined.pending
+
+		when:
+			"the first promise is fulfilled"
+			promise1.accept 1
+
+		then:
+			"the combined promise is still pending"
+			combined.pending
+
+		when:
+			"the second promise if fulfilled"
+			promise2.accept 2
+
+		then:
+			"the combined promise is fulfilled with both values"
+			combined.success
+			combined.get() == [1, 2]
+	}
+
+	def "A combined promise is rejected once any of its component promises are rejected"() {
+		given:
+			"two unfulfilled promises"
+			def promise1 = Promises.<Integer> defer().synchronousDispatcher().get()
+			def promise2 = Promises.<Integer> defer().synchronousDispatcher().get()
+
+		when:
+			"a combined promise is first created"
+			def combined = Promises.when(promise1, promise2)
+
+		then:
+			"it is pending"
+			combined.pending
+
+		when:
+			"a component promise is rejected"
+			promise1.accept new Exception()
+			println promise1.compose().debug()
+
+		then:
+			"the combined promise is rejected"
+			combined.error
+	}
+
+	def "A combined promise is immediately fulfilled if its component promises are already fulfilled"() {
+		given:
+			"two fulfilled promises"
+			def promise1 = Promises.success(1).get()
+			def promise2 = Promises.success(2).get()
+
+		when:
+			"a combined promise is first created"
+			def combined = Promises.when(promise1, promise2)
+
+		then:
+			"it is fulfilled"
+			combined.success
+			combined.get() == [1, 2]
+
+		when:
+			"promises are supplied"
+			promise1 = Promises.task(supplier { '1' }).get()
+			promise1.flush()
+			promise2 = Promises.task(supplier { '2' }).get()
+			promise2.flush()
+			combined = Promises.when(promise1, promise2)
+
+
+		then:
+			"it is fulfilled"
+			combined.success
+			combined.get() == ['1', '2']
+
+	}
+
+	def "A combined promise is eventually fulfilled"() {
+		given:
+			"one fulfilled and two unfulfilled promises"
+			def p1 = Promises.success(1).get()
+			def d1 = Promises.defer().get()
+			def d2 = Promises.defer().get()
+
+		when:
+			"a combined promise is created"
+			def combined = Promises.when(p1, d1.compose(), d2.compose());
+
+		then:
+			"combined promise is empty"
+			!combined.complete
+
+		when:
+			"values are accepted into unfulfilled promises"
+			d1.accept(2)
+			d2.accept(3)
+
+		then:
+			"combined promise is fulfilled"
+			combined.success
+			combined.get() == [1, 2, 3]
+	}
+
+	def "A combined promise through 'any' is fulfilled with the first component result when using synchronously"() {
+		given:
+			"two fulfilled promises"
+			def promise1 = Promises.success(1).get()
+			def promise2 = Promises.success(2).get()
+
+		when:
+			"a combined promise is first created"
+			def combined = Promises.any(promise1, promise2)
+
+		then:
+			"it is fulfilled"
+			combined.success
+			combined.get() == 1
+	}
+
+	def "A combined promise is immediately rejected if its component promises are already rejected"() {
+		given:
+			"two rejected promises"
+			def promise1 = Promises.error(new Exception()).synchronousDispatcher().get()
+			def promise2 = Promises.error(new Exception()).synchronousDispatcher().get()
+
+		when:
+			"a combined promise is first created"
+			def combined = Promises.when(promise1, promise2)
+			println combined.debug()
+
+		then:
+			"it is rejected"
+			combined.error
+	}
+
+	def "A single promise can be 'combined'"() {
+		given:
+			"one unfulfilled promise"
+			def promise1 = Promises.defer().synchronousDispatcher().get()
+
+		when:
+			"a combined promise is first created"
+			def combined = Promises.when(promise1)
+
+		then:
+			"it is pending"
+			combined.pending
+
+		when:
+			"the first promise is fulfilled"
+			println promise1.compose().debug()
+			promise1.accept 1
+
+		then:
+			"the combined promise is fulfilled"
+			combined.await() == [1]
+			combined.success
+	}
+
+	def "A promise can be fulfilled with a Supplier"() {
+		when:
+			"A promise configured with a supplier"
+			def promise = Promises.task(supplier { 1 }).get()
+
+		then:
+			"it is fulfilled"
+			promise.get() == 1
+			promise.success
+	}
+
+	def "A promise with a Supplier that throws an exception is rejected"() {
+		when:
+			"A promise configured with a supplier that throws an exception"
+			def promise = Promises.task(supplier { throw new RuntimeException() }).get()
+			promise.get()
+
+		then:
+			"it is rejected"
+			thrown RuntimeException
+			promise.error
+	}
+
+	def "A filtered promise is not fulfilled if the filter does not allow the value to pass through"() {
+		given:
+			"a promise with a filter that only accepts even values"
+			def promise = Promises.defer().synchronousDispatcher().get()
+			def filteredPromise = promise.compose().filter(predicate { it % 2 == 0 })
+
+		when:
+			"the promise is fulfilled with an odd value"
+			promise.accept 1
+
+		then:
+			"the filtered promise is not fulfilled"
+			filteredPromise.pending
+	}
+
+	def "A filtered promise is fulfilled if the filter allows the value to pass through"() {
+		given:
+			"a promise with a filter that only accepts even values"
+			def promise = Promises.defer().synchronousDispatcher().get()
+			def filteredPromise = promise.compose().filter(predicate { it % 2 == 0 })
+
+		when:
+			"the promise is fulfilled with an even value"
+			promise.accept 2
+
+		then:
+			"the filtered promise is fulfilled"
+			filteredPromise.success
+			filteredPromise.get() == 2
+	}
+
+	def "If a filter throws an exception the filtered promise is rejected"() {
+		given:
+			"a promise with a filter that throws an exception"
+			def promise = Promises.defer().synchronousDispatcher().get()
+			def e = new RuntimeException()
+			def filteredPromise = promise.compose().filter(predicate { throw e })
+
+		when:
+			"the promise is fulfilled"
+			promise.accept 2
+
+		then:
+			"the filtered promise is rejected"
+			filteredPromise.error
+	}
+
+	def "If a promise is already fulfilled with a value accepted by a filter the filtered promise is fulfilled"() {
+		given:
+			"a promise that is already fulfilled with an even value"
+			def promise = Promises.success(2).synchronousDispatcher().get()
+
+		when:
+			"the promise is filtered with a filter that only accepts even values"
+			def filteredPromise = promise.filter(predicate { it % 2 == 0 })
+
+		then:
+			"the filtered promise is fulfilled"
+			filteredPromise.success
+			filteredPromise.get() == 2
+	}
+
+	def "If a promise is already fulfilled with a value rejected by a filter, the filtered promise is not fulfilled"() {
+		given:
+			"a promise that is already fulfilled with an odd value"
+			def promise = Promises.success(1).synchronousDispatcher().get()
+
+		when:
+			"the promise is filtered with a filter that only accepts even values"
+			def filteredPromise = promise.filter(predicate { it % 2 == 0 })
+
+		then:
+			"the filtered promise is not fulfilled"
+			filteredPromise.pending
+	}
+
+	def "Errors stop compositions"() {
+		given:
+			"a promise"
+			def promiseDeferred = Promises.<String> defer()
+																		.env(new Environment())
+																		.dispatcher('eventLoop')
+																		.get()
+
+			final latch = new CountDownLatch(1)
+
+		when:
+			"p1 is consumed by p2"
+			Promise s = promiseDeferred.compose().then(function { Integer.parseInt it }, null).
+					when(NumberFormatException, consumer { latch.countDown() }).
+					then(function { println('not in log'); true }, null)
+
+		and:
+			"setting a value"
+			promiseDeferred.accept 'not a number'
+			s.await(5000, TimeUnit.MILLISECONDS)
+
+		then:
+			'No value'
+			thrown(RuntimeException)
+			latch.count == 0
+	}
+
+}
+
diff --git a/reactor-core/src/test/groovy/reactor/core/composable/spec/StreamsSpec.groovy b/reactor-core/src/test/groovy/reactor/core/composable/spec/StreamsSpec.groovy
new file mode 100644
index 0000000..600b00d
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/composable/spec/StreamsSpec.groovy
@@ -0,0 +1,1104 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.composable.spec
+
+import reactor.core.Environment
+import reactor.core.Observable
+import reactor.core.composable.Composable
+import reactor.core.composable.Deferred
+import reactor.core.composable.Stream
+import reactor.core.spec.Reactors
+import reactor.event.Event
+import reactor.event.selector.Selectors
+import reactor.function.Function
+import reactor.function.Supplier
+import reactor.tuple.Tuple2
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
+
+import static reactor.GroovyTestUtils.*
+
+class StreamsSpec extends Specification {
+
+	def 'A deferred Stream with an initial value makes that value available immediately'() {
+		given:
+			'a composable with an initial value'
+			Stream stream = Streams.defer('test').synchronousDispatcher().get()
+
+		when:
+			'the value is retrieved'
+			def value = stream.tap()
+			stream.flush()
+
+		then:
+			'it is available'
+			value.get() == 'test'
+	}
+
+	def 'A deferred Stream with an generated value makes that value available immediately'() {
+		given:
+			String test = ""
+			'a composable with an initial value'
+			Stream stream = Streams.defer(supplier { test }).synchronousDispatcher().get()
+
+		when:
+			'the value is retrieved'
+			def value = stream.tap()
+			test = "test"
+			stream.flush()
+
+		then:
+			'it is available'
+			value.get() == 'test'
+
+		when:
+			'nothing is provided'
+			Streams.defer((Supplier<Object>) null).synchronousDispatcher().get()
+
+		then:
+			"exception is thrown"
+			thrown(IllegalStateException)
+	}
+
+	def 'A Stream with a known set of values makes those values available immediately'() {
+		given:
+			'a composable with values 1 to 5 inclusive'
+			Stream s = Streams.defer([1, 2, 3, 4, 5]).synchronousDispatcher().get()
+
+		when:
+			'the first value is retrieved'
+			def first = s.first().tap()
+
+		and:
+			'the last value is retrieved'
+			def last = s.last().tap()
+			s.flush()
+
+		then:
+			'first and last'
+			first.get() == 1
+			last.get() == 5
+	}
+
+	def 'A Stream can be enforced to dispatch distinct values'() {
+		given:
+			'a composable with values 1 to 3 with duplicates'
+			Stream s = Streams.defer([1, 1, 2, 2, 3]).synchronousDispatcher().get()
+
+		when:
+			'the values are filtered and result is collected'
+			def tap = s.distinct().collect().tap()
+			s.flush()
+
+		then:
+			'collected must remove duplicates'
+			tap.get() == [1, 2, 3]
+	}
+
+	def 'A Stream with an unknown set of values makes those values available when flush predicate agrees'() {
+		given:
+			'a composable to defer'
+			Deferred d = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream s = d.compose()
+
+		when:
+			'a flushable propagate action is attached'
+			def tap = s.propagate([2, 3, 4, 5]).filter(predicate { it == 5 }).tap()
+
+		and:
+			'a flush trigger and a filtered tap are attached'
+			s.flushWhen(predicate { it == 1 })
+
+		and:
+			'values are accepted'
+			d.accept(1)
+
+		then:
+			'the filtered tap should see 5 from the propagated values'
+			tap.get() == 5
+			println s.debug()
+	}
+
+	def 'A Stream can be arbitrarely batched'() {
+		given:
+			'a composable to defer'
+			Deferred d = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream s = d.compose()
+
+		when:
+			'a batcher is created'
+			def b = d.batcher()
+			def tap = s.tap()
+
+		and:
+			'events are passed and a flush triggered'
+			b.accept(Event.wrap(1))
+			b.accept(Event.wrap(2))
+			b.accept(Event.wrap(3))
+			b.flush()
+
+		then:
+			'the filtered tap should see 5 from the propagated values'
+			tap.get() == 3
+			println s.debug()
+	}
+
+
+	def "A Stream's initial values are not passed to consumers but subsequent values are"() {
+		given:
+			'a composable with values 1 to 5 inclusive'
+			Stream stream = Streams.defer([1, 2, 3, 4, 5]).synchronousDispatcher().get()
+
+		when:
+			'a Consumer is registered'
+			def values = []
+			stream.consume(consumer { values << it })
+
+		then:
+			'it is not called with the initial values'
+			values == []
+
+		when:
+			'flush is called'
+			stream.flush()
+
+		then:
+			'the initial values are passed'
+			values == [1, 2, 3, 4, 5]
+
+
+	}
+
+	def 'Accepted values are passed to a registered Consumer'() {
+		given:
+			'a composable with a registered consumer'
+			Deferred d = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream composable = d.compose()
+			def value = composable.tap()
+
+		when:
+			'a value is accepted'
+			d.accept(1)
+
+		then:
+			'it is passed to the consumer'
+			value.get() == 1
+
+		when:
+			'another value is accepted'
+			d.accept(2)
+
+		then:
+			'it too is passed to the consumer'
+			value.get() == 2
+	}
+
+	def 'Accepted errors are passed to a registered Consumer'() {
+		given:
+			'a composable with a registered consumer of RuntimeExceptions'
+			Deferred d = Streams.<Integer> defer().synchronousDispatcher().get()
+			Composable composable = d.compose()
+			def errors = 0
+			composable.when(RuntimeException, consumer { errors++ })
+			println composable.debug()
+
+		when:
+			'A RuntimeException is accepted'
+			d.accept(new RuntimeException())
+
+		then:
+			'it is passed to the consumer'
+			errors == 1
+
+		when:
+			'A checked exception is accepted'
+			d.accept(new Exception())
+
+		then:
+			'it is not passed to the consumer'
+			errors == 1
+
+		when:
+			'A subclass of RuntimeException is accepted'
+			d.accept(new IllegalArgumentException())
+
+		then:
+			'it is passed to the consumer'
+			errors == 2
+	}
+
+	def 'A Stream can connect values and errors from another Stream'() {
+		given:
+			'a deferred composable with a consuming Stream'
+			Deferred<Integer, Stream<Integer>> parent = Streams.<Integer> defer().synchronousDispatcher().get()
+			Deferred<Integer, Stream<Integer>> child = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream s = child.compose()
+
+			Exception error = null
+			def value = s.tap()
+			s.when(Exception, consumer { error = it })
+			parent.compose().connect(s)
+
+		when:
+			'the parent accepts a value'
+			parent.accept(1)
+
+		then:
+			'it is passed to the child'
+			1 == value.get()
+
+		when:
+			"the parent accepts an error and the child's value is accessed"
+			parent.accept(new Exception())
+
+		then:
+			'the child contains the error from the parent'
+			error instanceof Exception
+	}
+
+	def 'A Stream can consume values from another Stream'() {
+		given:
+			'a deferred composable with a consuming Stream'
+			Deferred<Integer, Stream<Integer>> parent = Streams.<Integer> defer().synchronousDispatcher().get()
+			Deferred<Integer, Stream<Integer>> child = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream s = child.compose()
+
+			Exception error = null
+			def value = s.tap()
+			s.when(Exception, consumer { error = it })
+			parent.compose().connectValues(s)
+
+		when:
+			'the parent accepts a value'
+			parent.accept(1)
+
+		then:
+			'it is passed to the child'
+			1 == value.get()
+
+		when:
+			"the parent accepts an error and the child's value is accessed"
+			parent.accept(new Exception())
+
+		then:
+			'the child does not contains the error from the parent'
+			!error
+
+		when:
+			"try to consume itself"
+			parent.compose().connectValues(parent.compose())
+
+		then:
+			'the child contains the error from the parent'
+			thrown IllegalArgumentException
+	}
+
+	def 'When the accepted event is Iterable, split can iterate over values'() {
+		given:
+			'a composable with a known number of values'
+			Deferred<Iterable<String>, Stream<Iterable<String>>> d = Streams.<Iterable<String>> defer().get()
+			Stream<String> composable = d.compose().split()
+
+		when:
+			'accept list of Strings'
+			def tap = composable.tap()
+			d.accept(['a', 'b', 'c'])
+
+		then:
+			'its value is the last of the initial values'
+			tap.get() == 'c'
+
+	}
+
+	def 'When the number of values is unknown, last is never updated'() {
+		given:
+			'a composable that will accept an unknown number of values'
+			Deferred d = Streams.<Integer> defer().get()
+			Stream composable = d.compose()
+
+		when:
+			'last is retrieved'
+			composable.last().tap()
+
+		then:
+			"can't call last() on unbounded stream"
+			thrown(IllegalStateException)
+
+	}
+
+	def 'Last value of a batch is accessible'() {
+		given:
+			'a composable that will accept an unknown number of values'
+			Deferred d = Streams.<Integer> defer().batchSize(3).get()
+			Stream composable = d.compose()
+
+		when:
+			'the expected accept count is set and that number of values is accepted'
+			def tap = composable.last().tap()
+			d.accept(1)
+			d.accept(2)
+			d.accept(3)
+
+		then:
+			"last's value is now that of the last value"
+			tap.get() == 3
+
+		when:
+			'the expected accept count is set and that number of values is accepted'
+			tap = composable.last(3).tap()
+			d.accept(1)
+			d.accept(2)
+			d.accept(3)
+
+		then:
+			"last's value is now that of the last value"
+			tap.get() == 3
+	}
+
+	def "A Stream's values can be mapped"() {
+		given:
+			'a source composable with a mapping function'
+			Deferred source = Streams.<Integer> defer().get()
+			Stream mapped = source.compose().map(function { it * 2 })
+
+		when:
+			'the source accepts a value'
+			def value = mapped.tap()
+			source.accept(1)
+
+		then:
+			'the value is mapped'
+			value.get() == 2
+	}
+
+	def "Stream's values can be exploded"() {
+		given:
+			'a source composable with a mapMany function'
+			Deferred source = Streams.<Integer> defer().get()
+			Stream<Integer> mapped = source.compose().
+					mapMany(function { Integer v -> Streams.<Integer> defer(v * 2).get() })
+
+		when:
+			'the source accepts a value'
+			def value = mapped.tap()
+			source.accept(1)
+
+		then:
+			'the value is mapped'
+			value.get() == 2
+	}
+
+	def "Multiple Stream's values can be merged"() {
+		given:
+			'source composables to merge, collect and tap'
+			Deferred source1 = Streams.<Integer> defer().get()
+			Deferred source2 = Streams.<Integer> defer().get()
+			Deferred source3 = Streams.<Integer> defer().get()
+			def tap = source1.compose().merge(source2.compose(), source3.compose()).collect(3).tap()
+
+		when:
+			'the sources accept a value'
+			source1.accept(1)
+			source2.accept(2)
+			source3.accept(3)
+
+		then:
+			'the values are all collected from source1 stream'
+			tap.get() == [1, 2, 3]
+	}
+
+
+	def "Stream can be counted"() {
+		given:
+			'source composables to count and tap'
+			def source = Streams.<Integer> defer().get()
+			def countStream = Streams.<Long> defer().get().compose()
+			source.compose().count(countStream)
+			def tap = countStream.tap()
+
+		when:
+			'the sources accept a value'
+			source.accept(1)
+			source.accept(2)
+			source.accept(3)
+			source.flush()
+
+		then:
+			'the count value matches the number of accept'
+			tap.get() == 3
+	}
+
+	def "A Stream's values can be filtered"() {
+		given:
+			'a source composable with a filter that rejects odd values'
+			Deferred source = Streams.<Integer> defer().get()
+			Stream filtered = source.compose().filter(predicate { it % 2 == 0 })
+
+		when:
+			'the source accepts an even value'
+			def value = filtered.tap()
+			source.accept(2)
+
+		then:
+			'it passes through'
+			value.get() == 2
+
+		when:
+			'the source accepts an odd value'
+			source.accept(3)
+
+		then:
+			'it is blocked by the filter'
+			value.get() == 2
+
+		when:
+			'add a rejected stream'
+			Deferred rejectedSource = Streams.<Integer> defer().get()
+			def rejectedTap = rejectedSource.compose().tap()
+			filtered.filter(predicate { false }, rejectedSource.compose())
+			source.accept(2)
+			//println source.compose().debug()
+
+		then:
+			'it is rejected by the filter'
+			rejectedTap.get() == 2
+
+		when:
+			'an accept filter is assigned'
+			Deferred acceptedSource = Streams.<Integer> defer().get()
+			def acceptedTap = acceptedSource.compose().tap()
+			filtered.filter(predicate { true }, acceptedSource.compose())
+			source.accept(2)
+
+		then:
+			'the value is not passed to else composable'
+			null == acceptedTap.get()
+
+		when:
+			'value-aware filter'
+			Deferred anotherSource = Streams.<Integer> defer().get()
+			def tap = anotherSource.compose().filter(function { it == 2 }).tap()
+			anotherSource.accept(2)
+
+		then:
+			'it is accepted by the filter'
+			tap.get() == 2
+
+		when:
+			'simple filter'
+			anotherSource = Streams.<Boolean> defer().get()
+			tap = anotherSource.compose().filter().tap()
+			anotherSource.accept(true)
+
+		then:
+			'it is accepted by the filter'
+			tap.get()
+
+		when:
+			'simple filter nominal case'
+			anotherSource = Streams.<Boolean> defer().get()
+			tap = anotherSource.compose().filter().tap()
+			anotherSource.accept(false)
+
+		then:
+			'it is not accepted by the filter'
+			!tap.get()
+	}
+
+	def "When a mapping function throws an exception, the mapped composable accepts the error"() {
+		given:
+			'a source composable with a mapping function that throws an exception'
+			Deferred source = Streams.<Integer> defer().get()
+			Stream mapped = source.compose().map(function { if (it == 1) throw new RuntimeException() else 'na' })
+			def errors = 0
+			mapped.when(Exception, consumer { errors++ })
+
+		when:
+			'the source accepts a value'
+			source.accept(1)
+
+		then:
+			'the error is passed on'
+			errors == 1
+	}
+
+	def "When a filter function throws an exception, the filtered composable accepts the error"() {
+		given:
+			'a source composable with a filter function that throws an exception'
+			Deferred source = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream filtered = source.compose().filter(predicate { if (it == 1) throw new RuntimeException() else true })
+			def errors = 0
+			filtered.when(Exception, consumer { errors++ })
+
+		when:
+			'the source accepts a value'
+			source.accept(1)
+
+		then:
+			'the error is passed on'
+			errors == 1
+	}
+
+	def "A known set of values can be reduced"() {
+		given:
+			'a composable with a known set of values'
+			Stream source = Streams.defer([1, 2, 3, 4, 5]).synchronousDispatcher().get()
+
+		when:
+			'a reduce function is registered'
+			def reduced = source.reduce(new Reduction())
+			def value = reduced.tap()
+			reduced.flush()
+
+		then:
+			'the resulting composable holds the reduced value'
+			value.get() == 120
+
+		when:
+			'use an initial value'
+			value = source.reduce(new Reduction(), 2).tap()
+			source.flush()
+
+		then:
+			'the updated reduction is available'
+			value.get() == 240
+	}
+
+	def "When reducing a known set of values, only the final value is passed to consumers"() {
+		given:
+			'a composable with a known set of values and a reduce function'
+			Stream reduced = Streams.defer([1, 2, 3, 4, 5]).
+					synchronousDispatcher().
+					get().
+					reduce(new Reduction())
+
+		when:
+			'a consumer is registered'
+			def values = []
+			reduced.consume(consumer { values << it }).flush()
+
+		then:
+			'the consumer only receives the final value'
+			values == [120]
+	}
+
+	def "When reducing a known number of values, only the final value is passed to consumers"() {
+		given:
+			'a composable with a known number of values and a reduce function'
+			Deferred source = Streams.<Integer> defer().batchSize(5).synchronousDispatcher().get()
+			Stream reduced = source.compose().reduce(new Reduction())
+			def values = []
+			reduced.consume(consumer { values << it })
+
+		when:
+			'the expected number of values is accepted'
+			source.accept(1)
+			source.accept(2)
+			source.accept(3)
+			source.accept(4)
+			source.accept(5)
+
+		then:
+			'the consumer only receives the final value'
+			values == [120]
+	}
+
+	def 'A known number of values can be reduced'() {
+		given:
+			'a composable that will accept 5 values and a reduce function'
+			Deferred source = Streams.<Integer> defer().batchSize(5).synchronousDispatcher().get()
+			Stream reduced = source.compose().reduce(new Reduction())
+			def value = reduced.tap()
+
+		when:
+			'the expected number of values is accepted'
+			source.accept(1)
+			source.accept(2)
+			source.accept(3)
+			source.accept(4)
+			source.accept(5)
+
+		then:
+			'the reduced composable holds the reduced value'
+			value.get() == 120
+	}
+
+	def 'When a known number of values is being reduced, only the final value is made available'() {
+		given:
+			'a composable that will accept 2 values and a reduce function'
+			Deferred source = Streams.<Integer> defer().batchSize(2).synchronousDispatcher().get()
+			def value = source.compose().reduce(new Reduction()).tap()
+
+		when:
+			'the first value is accepted'
+			source.accept(1)
+
+		then:
+			'the reduced value is unknown'
+			value.get() == null
+
+		when:
+			'the second value is accepted'
+			source.accept(2)
+
+		then:
+			'the reduced value is known'
+			value.get() == 2
+	}
+
+	def 'When an unknown number of values is being reduced, each reduction is passed to a consumer on flush'() {
+		given:
+			'a composable with a reduce function'
+			Deferred<Integer, Stream<Integer>> source = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream reduced = source.compose().reduce(new Reduction())
+			def value = reduced.tap()
+
+		when:
+			'the first value is accepted'
+			source.accept(1)
+
+		then:
+			'the reduction is not available'
+			!value.get()
+
+		when:
+			'the second value is accepted and flushed'
+			source.accept(2)
+			source.flush()
+
+		then:
+			'the updated reduction is available'
+			value.get() == 2
+
+		when:
+			'use an initial value'
+			value = source.compose().reduce(new Reduction(), 2).tap()
+			source.accept(1)
+			source.flush()
+
+		then:
+			'the updated reduction is available'
+			value.get() == 2
+	}
+
+	def 'When an unknown number of values is being scanned, each reduction is passed to a consumer'() {
+		given:
+			'a composable with a reduce function'
+			Deferred<Integer, Stream<Integer>> source = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream reduced = source.compose().scan(new Reduction())
+			def value = reduced.tap()
+
+		when:
+			'the first value is accepted'
+			source.accept(1)
+
+		then:
+			'the reduction is available'
+			value.get() == 1
+
+		when:
+			'the second value is accepted'
+			source.accept(2)
+
+		then:
+			'the updated reduction is available'
+			value.get() == 2
+
+		when:
+			'use an initial value'
+			value = source.compose().scan(new Reduction(), 4).tap()
+			source.accept(1)
+			source.flush()
+
+		then:
+			'the updated reduction is available'
+			value.get() == 4
+	}
+
+
+	def 'Reduce will accumulate a list of accepted values'() {
+		given:
+			'a composable'
+			Deferred source = Streams.<Integer> defer().batchSize(1).synchronousDispatcher().get()
+			Stream reduced = source.compose().collect()
+			def value = reduced.tap()
+
+		when:
+			'the first value is accepted'
+			source.accept(1)
+
+		then:
+			'the list contains the first element'
+			value.get() == [1]
+	}
+
+	def 'Buffer will accumulate a list of accepted values and pass it to a consumer one by one'() {
+		given:
+			'a source and a collected stream'
+			Deferred source = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream reduced = source.compose().buffer()
+			def value = reduced.tap()
+
+		when:
+			'the first value is accepted on the source'
+			source.accept(1)
+
+		then:
+			'the collected list is not yet available'
+			value.get() == null
+
+		when:
+			'the second value is accepted'
+			source.accept(2)
+			reduced.flush()
+
+		then:
+			'the tapped value contains the last element'
+			value.get() == 2
+	}
+
+	def 'BufferWithErrors will accumulate a list of accepted values or errors and pass it to a consumer one by one'() {
+		given:
+			'a source and a collected stream'
+			Deferred source = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream reduced = source.compose().bufferWithErrors()
+
+			def error = 0
+			def value = reduced.when(Exception, consumer { error++ }).tap()
+
+		when:
+			'the first value is accepted on the source'
+			source.accept(1)
+
+		then:
+			'the collected list is not yet available'
+			value.get() == null
+
+		when:
+			'the second value is accepted'
+			source.accept(new Exception())
+			reduced.flush()
+			println reduced.debug()
+
+		then:
+			'the error consumer has been invoked and the tapped value is 1'
+			value.get() == 1
+			error == 1
+	}
+
+	def 'Collect will accumulate a list of accepted values and pass it to a consumer'() {
+		given:
+			'a source and a collected stream'
+			Deferred source = Streams.<Integer> defer().batchSize(2).synchronousDispatcher().get()
+			Stream reduced = source.compose().collect()
+			def value = reduced.tap()
+
+		when:
+			'the first value is accepted on the source'
+			source.accept(1)
+
+		then:
+			'the collected list is not yet available'
+			value.get() == null
+
+		when:
+			'the second value is accepted'
+			source.accept(2)
+
+		then:
+			'the collected list contains the first and second elements'
+			value.get() == [1, 2]
+	}
+
+	def 'Collect will accumulate a list of accepted values until flush and pass it to a consumer'() {
+		given:
+			'a source and a collected stream'
+			Deferred source = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream reduced = source.compose().collect()
+			def value = reduced.tap()
+
+		when:
+			'the first value is accepted on the source'
+			source.accept(1)
+
+		then:
+			'the collected list is not yet available'
+			value.get() == null
+
+		when:
+			'the second value is accepted'
+			source.accept(2)
+			source.flush()
+
+		then:
+			'the collected list contains the first and second elements'
+			value.get() == [1, 2]
+	}
+
+	def 'Creating Stream from environment'() {
+		given:
+			'a source stream with a given environment'
+			Environment environment = new Environment()
+			Deferred source = Streams.<Integer> defer(environment, 'ringBuffer')
+			Deferred source2 = Streams.<Integer> defer(environment)
+
+		when:
+			'accept a value'
+			CountDownLatch latch = new CountDownLatch(2)
+			def v = ""
+			source.compose().consume(consumer { v = 'ok'; latch.countDown() })
+			source2.compose().consume(consumer { v = 'ok'; latch.countDown() })
+			source.accept(1)
+			source2.accept(1)
+
+		then:
+			'dispatching works'
+			latch.await(1000, TimeUnit.MILLISECONDS)
+			v == 'ok'
+
+
+		cleanup:
+			environment.shutdown()
+	}
+
+	def 'Creating Stream from observable'() {
+		given:
+			'a source stream with a given observable'
+			def r = Reactors.reactor().synchronousDispatcher().get()
+			def selector = Selectors.anonymous()
+			Event<Integer> event = null
+			Streams.<Integer> on(r, selector).consumeEvent(consumer { event = it })
+
+		when:
+			'accept a value'
+			r.notify(selector.object, Event.wrap(1))
+
+		then:
+			'dispatching works'
+			event
+			event.data == 1
+	}
+
+	def 'Window will accumulate a list of accepted values and pass it to a consumer on the specified period'() {
+		given:
+			'a source and a collected stream'
+			Environment environment = new Environment()
+			Deferred source = Streams.<Integer> defer().synchronousDispatcher().env(environment).get()
+			Stream reduced = source.compose().window(500)
+			def value = reduced.tap()
+
+		when:
+			'the first values are accepted on the source'
+			source.accept(1)
+			source.accept(2)
+			sleep(1200)
+
+		then:
+			'the collected list is available'
+			value.get() == [1, 2]
+
+		when:
+			'the second value is accepted'
+			source.accept(3)
+			source.accept(4)
+			sleep(1200)
+
+		then:
+			'the collected list contains the first and second elements'
+			value.get() == [3, 4]
+
+		cleanup:
+			environment.shutdown()
+
+	}
+
+	def 'Collect with Timeout will accumulate a list of accepted values and pass it to a consumer'() {
+		given:
+			'a source and a collected stream'
+			Environment environment = new Environment()
+			Deferred source = Streams.<Integer> defer().synchronousDispatcher().env(environment).get()
+			Stream reduced = source.compose().collect(5).timeout(1000)
+			def value = reduced.tap()
+
+		when:
+			'the first values are accepted on the source'
+			source.accept(1)
+			source.accept(1)
+			source.accept(1)
+			source.accept(1)
+			source.accept(1)
+
+		then:
+			'the collected list is not yet available'
+			value.get() == [1, 1, 1, 1, 1]
+
+		when:
+			'the second value is accepted'
+			source.accept(2)
+			source.accept(2)
+			sleep(1500)
+
+		then:
+			'the collected list contains the first and second elements'
+			value.get() == [2, 2]
+
+		cleanup:
+			environment.shutdown()
+
+	}
+
+	def 'Moving Window accumulate items without dropping previous'() {
+		given:
+			'a source and a collected stream'
+			Environment environment = new Environment()
+			Deferred source = Streams.<Integer> defer().synchronousDispatcher().env(environment).get()
+			Stream reduced = source.compose().movingWindow(500, 5)
+			def value = reduced.tap()
+
+		when:
+			'the window accepts first items'
+			source.accept(1)
+			source.accept(2)
+			sleep(1200)
+
+		then:
+			'it outputs received values'
+			value.get() == [1, 2]
+
+		when:
+			'the window accepts following items'
+			source.accept(3)
+			source.accept(4)
+			sleep(1200)
+
+		then:
+			'it outputs received values'
+			value.get() == [1, 2, 3, 4]
+
+		when:
+			'the starts dropping items on overflow'
+			source.accept(5)
+			source.accept(6)
+			sleep(1200)
+
+		then:
+			'it outputs received values'
+			value.get() == [2, 3, 4, 5, 6]
+
+		cleanup:
+			environment.shutdown()
+	}
+
+	def 'Moving Window will drop overflown items'() {
+		given:
+			'a source and a collected stream'
+			Environment environment = new Environment()
+			Deferred source = Streams.<Integer> defer().synchronousDispatcher().env(environment).get()
+			Stream reduced = source.compose().movingWindow(500, 5)
+			def value = reduced.tap()
+
+		when:
+			'the window overflows'
+			source.accept(1)
+			source.accept(2)
+			source.accept(3)
+			source.accept(4)
+			source.accept(5)
+			source.accept(6)
+			sleep(1200)
+
+		then:
+			'it outputs values dismissing outdated ones'
+			value.get() == [2, 3, 4, 5, 6]
+
+		cleanup:
+			environment.shutdown()
+	}
+
+	def 'Collect will accumulate values from multiple threads'() {
+		given:
+			'a source and a collected stream'
+			def sum = new AtomicInteger()
+			def latch = new CountDownLatch(3)
+			Environment env = new Environment()
+			Deferred head = Streams.<Integer> defer().
+					env(env).
+					batchSize(333).
+					dispatcher(Environment.THREAD_POOL).
+					get()
+			Stream tail = head.compose().collect()
+			tail.consume(consumer { List<Integer> ints ->
+				println ints.size()
+				sum.addAndGet(ints.size())
+				latch.countDown()
+			})
+
+		when:
+			'values are accepted into the head'
+			(1..1000).each { head.accept(it) }
+
+		then:
+			'results contains the expected values'
+			latch.await(5, TimeUnit.SECONDS)
+			sum.get() == 999
+	}
+
+	def 'An Observable can consume values from a Stream'() {
+		given:
+			'a Stream and a Observable consumer'
+			Deferred d = Streams.<Integer> defer().synchronousDispatcher().get()
+			Stream composable = d.compose()
+			Observable observable = Mock(Observable)
+			composable.consume('key', observable)
+
+		when:
+			'the composable accepts a value'
+			d.accept(1)
+
+		then:
+			'the observable is notified'
+			1 * observable.notify('key', _)
+	}
+
+	def 'An observable can consume values from a Stream with a known set of values'() {
+		given:
+			'a Stream with 3 values'
+			Stream stream = Streams.defer([1, 2, 3]).synchronousDispatcher().get()
+			Observable observable = Mock(Observable)
+
+		when:
+			'a stream consumer is registerd'
+			stream.consume('key', observable)
+
+			stream.flush()
+
+		then:
+			'the observable is notified of the values'
+			3 * observable.notify('key', _)
+	}
+
+	static class Reduction implements Function<Tuple2<Integer, Integer>, Integer> {
+		@Override
+		public Integer apply(Tuple2<Integer, Integer> reduce) {
+			def result = reduce.t2 == null ? 1 : reduce.t1 * reduce.t2
+			println "${reduce?.t2} ${reduce?.t1} reduced to ${result}"
+			return result
+		}
+	}
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/configuration/PropertiesConfigurationReaderSpec.groovy b/reactor-core/src/test/groovy/reactor/core/configuration/PropertiesConfigurationReaderSpec.groovy
new file mode 100644
index 0000000..5d0243a
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/configuration/PropertiesConfigurationReaderSpec.groovy
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.core.configuration
+
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class PropertiesConfigurationReaderSpec extends Specification {
+
+	def "Default configuration can be read"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "the default configuration is read"
+			def configuration = reader.read()
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "it contains the expected dispatchers"
+			configuration.defaultDispatcherName == 'ringBuffer'
+			dispatchers
+			matchesExpectedDefaultConfiguration(dispatchers.eventLoop, DispatcherType.EVENT_LOOP, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.ringBuffer, DispatcherType.RING_BUFFER, null, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.workQueue, DispatcherType.WORK_QUEUE, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.threadPoolExecutor, DispatcherType.THREAD_POOL_EXECUTOR, 0, 2048)
+	}
+
+	def "Custom default configuration can be read"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "a custom default configuration is read"
+			System.setProperty("reactor.profiles.default", "custom")
+			def configuration = reader.read()
+			System.clearProperty("reactor.profiles.active")
+
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "it contains the expected dispatchers"
+			dispatchers.size() == 1
+			matchesExpectedDefaultConfiguration(dispatchers.alpha, DispatcherType.SYNCHRONOUS, null, null)
+	}
+
+	def "Additional profiles can be enabled"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "an additional profile is active"
+			System.setProperty("reactor.profiles.active", "custom")
+			def configuration = reader.read()
+			System.clearProperty("reactor.profiles.active")
+
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "it contains the expected dispatchers"
+			dispatchers.size() == 5
+			matchesExpectedDefaultConfiguration(dispatchers.eventLoop, DispatcherType.EVENT_LOOP, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.ringBuffer, DispatcherType.RING_BUFFER, null, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.threadPoolExecutor, DispatcherType.THREAD_POOL_EXECUTOR, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.workQueue, DispatcherType.WORK_QUEUE, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.alpha, DispatcherType.SYNCHRONOUS, null, null)
+	}
+
+	def "An active profile can override a profile that came before it"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "two active profiles are enabled"
+			System.setProperty("reactor.profiles.active", "custom, override-custom")
+			def configuration = reader.read()
+			System.clearProperty("reactor.profiles.active")
+
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "the later profile overrides the earlier profile"
+			dispatchers.size() == 5
+			matchesExpectedDefaultConfiguration(dispatchers.eventLoop, DispatcherType.EVENT_LOOP, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.ringBuffer, DispatcherType.RING_BUFFER, null, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.threadPoolExecutor, DispatcherType.THREAD_POOL_EXECUTOR, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.workQueue, DispatcherType.WORK_QUEUE, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.alpha, DispatcherType.RING_BUFFER, null, null)
+	}
+
+	def "A active profile can override the default profile"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "a profile is enabled"
+			System.setProperty("reactor.profiles.active", "override-default")
+			def configuration = reader.read()
+			System.clearProperty("reactor.profiles.active")
+
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "the active profile overrides the default profile"
+			dispatchers.size() == 4
+			matchesExpectedDefaultConfiguration(dispatchers.eventLoop, DispatcherType.EVENT_LOOP, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.ringBuffer, DispatcherType.RING_BUFFER, null, 512)
+			matchesExpectedDefaultConfiguration(dispatchers.workQueue, DispatcherType.WORK_QUEUE, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.threadPoolExecutor, DispatcherType.THREAD_POOL_EXECUTOR, 0, 2048)
+	}
+
+	def "A system property can override existing configuration"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "a profile is enabled and a system property override is set"
+			System.setProperty("reactor.profiles.active", "override-default, custom")
+			System.setProperty("reactor.dispatchers.alpha.type", "eventLoop")
+			def configuration = reader.read()
+			System.clearProperty("reactor.profiles.active")
+			System.clearProperty("reactor.dispatchers.alpha.type")
+
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "the system property takes precedence"
+			dispatchers.size() == 5
+			matchesExpectedDefaultConfiguration(dispatchers.eventLoop, DispatcherType.EVENT_LOOP, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.ringBuffer, DispatcherType.RING_BUFFER, null, 512)
+			matchesExpectedDefaultConfiguration(dispatchers.workQueue, DispatcherType.WORK_QUEUE, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.threadPoolExecutor, DispatcherType.THREAD_POOL_EXECUTOR, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.alpha, DispatcherType.EVENT_LOOP, null, null)
+	}
+
+	def "Missing active profiles are tolerated"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "a non-existent profile is enabled"
+			System.setProperty("reactor.profiles.active", "does-not-exist")
+			def configuration = reader.read()
+			System.clearProperty("reactor.profiles.active")
+
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "its absence is tolerated"
+			dispatchers.size() == 4
+			matchesExpectedDefaultConfiguration(dispatchers.eventLoop, DispatcherType.EVENT_LOOP, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.ringBuffer, DispatcherType.RING_BUFFER, null, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.workQueue, DispatcherType.WORK_QUEUE, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.threadPoolExecutor, DispatcherType.THREAD_POOL_EXECUTOR, 0, 2048)
+	}
+
+	def "Missing default profile is tolerated"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "a non-existent default profile is specified"
+			System.setProperty("reactor.profiles.default", "does-not-exist")
+			System.setProperty("reactor.dispatchers.default", "alpha")
+			def configuration = reader.read()
+			System.clearProperty("reactor.profiles.default")
+			System.clearProperty("reactor.dispatchers.default")
+
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "its absence is tolerated"
+			dispatchers.size() == 0
+	}
+
+	def "Dispatchers with unrecognized type are tolerated"() {
+		given: "a configuration reader"
+			def reader = new PropertiesConfigurationReader()
+
+		when: "a profile containing an unrecognized dispatcher type is enabled"
+			System.setProperty("reactor.profiles.active", "unrecognized-type")
+			def configuration = reader.read()
+			System.clearProperty("reactor.profiles.active")
+
+			def dispatchers = toMapByName configuration.dispatcherConfigurations
+
+		then: "the unrecognized dispatcher type is tolerated"
+			dispatchers.size() == 4
+			matchesExpectedDefaultConfiguration(dispatchers.eventLoop, DispatcherType.EVENT_LOOP, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.ringBuffer, DispatcherType.RING_BUFFER, null, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.workQueue, DispatcherType.WORK_QUEUE, 0, 2048)
+			matchesExpectedDefaultConfiguration(dispatchers.threadPoolExecutor, DispatcherType.THREAD_POOL_EXECUTOR, 0, 2048)
+	}
+
+	def cleanup() {
+		System.clearProperty('reactor.profiles.default')
+	}
+
+	def matchesExpectedDefaultConfiguration(dispatcherConfiguration, type, size, backlog) {
+		dispatcherConfiguration.type == type &&
+				dispatcherConfiguration.size == size &&
+				dispatcherConfiguration.backlog == backlog
+	}
+
+	def toMapByName(dispatcherConfigurations) {
+		def dispatchersByName = [:];
+		dispatcherConfigurations.each { configuration -> dispatchersByName[configuration.name] = configuration }
+		dispatchersByName
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/fork/ForkJoinPoolSpec.groovy b/reactor-core/src/test/groovy/reactor/core/fork/ForkJoinPoolSpec.groovy
new file mode 100644
index 0000000..44a47a5
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/fork/ForkJoinPoolSpec.groovy
@@ -0,0 +1,68 @@
+package reactor.core.fork
+
+import reactor.core.Environment
+import reactor.function.Function
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import static reactor.GroovyTestUtils.consumer
+import static reactor.GroovyTestUtils.function
+
+/**
+ * @author Jon Brisbin
+ */
+class ForkJoinPoolSpec extends Specification {
+
+	Random random
+	Environment env
+	ForkJoinPool pool
+	Function task
+
+	def setup() {
+		random = new Random(System.nanoTime())
+		env = new Environment()
+		pool = new ForkJoinPool(env)
+		task = function { v ->
+			Thread.sleep(random.nextInt(500))
+			return Thread.currentThread()
+		}
+	}
+
+	def "ForkJoinPool forks tasks"() {
+
+		given: "a standard pool"
+			def main = Thread.currentThread()
+
+		when: "tasks are forked"
+			def task = pool.join(task, task, task, task)
+			def results = task.compose()
+			task.submit()
+
+		then: "tasks were run in another thread"
+			!results.await(5, TimeUnit.SECONDS)?.find { it == main }
+
+	}
+
+	def "ForkJoinPool collects values"() {
+
+		given: "a standard pool"
+			def latch = new CountDownLatch(4)
+
+		when: "tasks are forked"
+			def fj = pool.fork()
+			fj.compose().
+					collect(4).
+					consume(consumer { threads -> threads.each { latch.countDown() } })
+			(1..4).each {
+				fj.add(task).submit()
+			}
+			fj.submit()
+
+		then: "tasks were run in another thread"
+			latch.await(5, TimeUnit.SECONDS)
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/processor/ProcessorsSpec.groovy b/reactor-core/src/test/groovy/reactor/core/processor/ProcessorsSpec.groovy
new file mode 100644
index 0000000..0a4f2c3
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/processor/ProcessorsSpec.groovy
@@ -0,0 +1,101 @@
+package reactor.core.processor
+
+import reactor.function.Consumer
+import reactor.function.Supplier
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Jon Brisbin
+ */
+class ProcessorsSpec extends Specification {
+
+	def "Processor provides high-speed event processor"() {
+
+		given: 'a Processor for events'
+		def latch = new CountDownLatch(10)
+		List<Data> data = []
+		def processor = new reactor.core.processor.spec.ProcessorSpec<Data>().
+				dataSupplier({ new Data() } as Supplier<Data>).
+				consume({ Data d -> data << d; latch.countDown() } as Consumer<Data>).
+				get()
+
+		when: 'a series of events are triggered'
+		(1..10).each {
+			def op = processor.prepare()
+			op.get().type = "test"
+			op.commit()
+		}
+
+		then: 'the Consumers were run'
+		latch.await(1, TimeUnit.SECONDS)
+		data.size() == 10
+
+		cleanup:
+		processor.shutdown()
+
+	}
+
+	def "Processor provides for multiple event handlers"() {
+
+		given: 'a Processor for events'
+		def latch = new CountDownLatch(3)
+		def data = []
+		def consumer = { Data d -> data << d.data; latch.countDown() } as Consumer<Data>
+		def processor = new reactor.core.processor.spec.ProcessorSpec<Data>().
+				dataSupplier({ new Data() } as Supplier<Data>).
+				consume(consumer).
+				consume(consumer).
+				consume(consumer).
+				get()
+
+		when: 'a series of events are triggered'
+		def i = 0
+		processor.batch(3, { Data d ->
+			d.type = "test"
+			d.data = "run ${i++}"
+		} as Consumer<Data>)
+
+		then: 'the Consumers were run'
+		latch.await(1, TimeUnit.SECONDS)
+		data.size() == 3
+
+		cleanup:
+		processor.shutdown()
+
+	}
+
+	def "Processor can handle batches larger than the backlog"() {
+
+		given: 'a Processor for events'
+		def latch = new CountDownLatch(300)
+		def consumer = { Data d -> latch.countDown() } as Consumer<Data>
+		def processor = new reactor.core.processor.spec.ProcessorSpec<Data>().
+				dataBufferSize(128).
+				dataSupplier({ new Data() } as Supplier<Data>).
+				consume(consumer).
+				get()
+
+		when: 'a series of events are triggered'
+		def i = 0
+		processor.batch(300, { Data d ->
+			d.type = "test"
+			d.data = "run ${i++}"
+		} as Consumer<Data>)
+
+		then: 'the Consumers were run'
+		latch.await(1, TimeUnit.SECONDS)
+
+		cleanup:
+		processor.shutdown()
+
+	}
+
+}
+
+class Data {
+	String type
+	String data
+}
diff --git a/reactor-core/src/test/groovy/reactor/core/spec/ComponentSpecSpec.groovy b/reactor-core/src/test/groovy/reactor/core/spec/ComponentSpecSpec.groovy
new file mode 100644
index 0000000..548077e
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/core/spec/ComponentSpecSpec.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.core.spec
+
+import reactor.core.composable.Deferred
+import reactor.core.composable.Promise
+import reactor.core.composable.spec.Promises
+import reactor.core.composable.spec.Streams
+import reactor.event.dispatch.TraceableDelegatingDispatcher
+import reactor.function.support.Tap
+import spock.lang.Specification
+
+/**
+ * @author Stephane Maldini
+ */
+class ComponentSpecSpec extends Specification {
+
+	def "Reactor correctly built"() {
+
+		when:
+			"we create a plain Reactor"
+			def reactor = Reactors.reactor().synchronousDispatcher().get()
+
+		then:
+			reactor.core.Reactor.isAssignableFrom(reactor.class)
+	}
+
+	def "Tracing is enabled"() {
+
+		when:
+			"a traceable Reactor is created"
+			def reactor = Reactors.reactor().synchronousDispatcher().traceEventPath().get()
+
+		then:
+			"Reactor uses traceable components"
+			reactor.getDispatcher() instanceof TraceableDelegatingDispatcher
+	}
+
+	def "Composable correctly built"() {
+
+		when:
+			"we create a plain Composable"
+			Deferred composable = Streams.defer().synchronousDispatcher().get()
+			Tap<String> tap = composable.compose().tap()
+			composable.accept('test')
+
+		then:
+			Deferred.isAssignableFrom(composable.class)
+			tap.get() == 'test'
+
+	}
+
+	def "Promise correctly built"() {
+
+		when:
+			"we create a plain Promise"
+			Promise promise = Promises.success('test').synchronousDispatcher().get()
+
+		then:
+			Promise.isAssignableFrom(promise.class)
+			promise.get() == 'test'
+	}
+
+	def "Deferred Promise correctly built"() {
+
+		when:
+			"we create a plain Promise"
+			Deferred promise = Promises.defer().get()
+			promise.accept 'test'
+
+		then:
+			Deferred.isAssignableFrom(promise.class)
+			promise.compose().get() == 'test'
+	}
+
+}
+
diff --git a/reactor-core/src/test/groovy/reactor/dispatch/ConsumerFilteringEventRouterSpec.groovy b/reactor-core/src/test/groovy/reactor/dispatch/ConsumerFilteringEventRouterSpec.groovy
new file mode 100644
index 0000000..985588c
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/dispatch/ConsumerFilteringEventRouterSpec.groovy
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+
+
+package reactor.dispatch
+
+import reactor.filter.PassThroughFilter
+import reactor.function.Consumer
+import reactor.event.Event
+import reactor.event.registry.Registration;
+import reactor.event.routing.ConsumerFilteringEventRouter;
+import reactor.event.routing.ConsumerInvoker;
+import reactor.event.selector.Selector;
+import spock.lang.Specification
+
+class ConsumerFilteringEventRouterSpec extends Specification {
+
+	def "Basic event routing"() {
+		def filter = new PassThroughFilter()
+		def consumerInvoker = Mock(ConsumerInvoker)
+		def completionConsumer = Mock(Consumer)
+		def errorConsumer = Mock(Consumer)
+		def consumer = Mock(Consumer)
+		def event = new Event("data")
+
+		given: "A consumer filtering event router"
+		def eventRouter = new ConsumerFilteringEventRouter(filter, consumerInvoker)
+
+		when: "An event is routed to a single registered consumer"
+		Registration registration = Mock(Registration)
+		registration.getObject() >> consumer
+		registration.getSelector() >> Mock(Selector)
+
+		eventRouter.route("key", event, [registration], completionConsumer, errorConsumer)
+
+		then: "The consumerInvoker is called to invoke the consumer and the completionConsumer"
+		1 * consumerInvoker.invoke(consumer, _, event)
+		1 * consumerInvoker.invoke(completionConsumer, _, event)
+		0 * errorConsumer.accept(_)
+	}
+
+	def "Events are not routed to paused consumers"() {
+		def filter = new PassThroughFilter()
+		def consumerInvoker = Mock(ConsumerInvoker)
+		def completionConsumer = Mock(Consumer)
+		def errorConsumer = Mock(Consumer)
+		def consumer = Mock(Consumer)
+		def event = new Event("data")
+
+		given: "A consumer filtering event router"
+		def eventRouter = new ConsumerFilteringEventRouter(filter, consumerInvoker)
+
+		when: "an event is routed to a paused consumer"
+		Registration registration = Mock()
+		registration.getObject() >> consumer
+		registration.getSelector() >> Mock(Selector)
+		registration.isPaused() >> true
+
+		eventRouter.route("key", event, [registration], completionConsumer, errorConsumer)
+
+		then: "the consumer invoker is only called to invoke the completionConsumer"
+		0 * consumerInvoker.invoke(consumer, _, _, _)
+		1 * consumerInvoker.invoke(completionConsumer, _, event)
+		0 * errorConsumer.accept(_)
+	}
+
+	def "Events are not routed to cancelled consumers"() {
+		def filter = new PassThroughFilter()
+		def consumerInvoker = Mock(ConsumerInvoker)
+		def completionConsumer = Mock(Consumer)
+		def errorConsumer = Mock(Consumer)
+		def consumer = Mock(Consumer)
+		def event = new Event("data")
+
+		given: "A consumer filtering event router"
+		def eventRouter = new ConsumerFilteringEventRouter(filter, consumerInvoker)
+
+		when: "an event is routed to a paused consumer"
+		Registration registration = Mock(Registration)
+		registration.getObject() >> consumer
+		registration.getSelector() >> Mock(Selector)
+		registration.isCancelled() >> true
+
+		eventRouter.route("key", event, [registration], completionConsumer, errorConsumer)
+
+		then: "the consumer invoker is only called to invoke the completion consumer"
+		0 * consumerInvoker.invoke(consumer, _, _, _)
+		1 * consumerInvoker.invoke(completionConsumer, _, event)
+		0 * errorConsumer.accept(_)
+	}
+
+	def "Consumers configured to be cancelled after use are cancelled once they've been used"() {
+		def filter = new PassThroughFilter()
+		def consumerInvoker = Mock(ConsumerInvoker)
+		def completionConsumer = Mock(Consumer)
+		def errorConsumer = Mock(Consumer)
+		def consumer = Mock(Consumer)
+		def event = new Event("data")
+
+		given: "A consumer filtering event router"
+		def eventRouter = new ConsumerFilteringEventRouter(filter, consumerInvoker)
+		Registration registration = Mock(Registration)
+
+		when: "an event is routed to a cancel after use consumer"
+		registration.getObject() >> consumer
+		registration.getSelector() >> Mock(Selector)
+		registration.isCancelAfterUse() >> true
+
+		eventRouter.route("key", event, [registration], completionConsumer, errorConsumer)
+
+		then: "the consumer invoker is called to invoke the consumer and completion consumer and the consumer is cancelled"
+		1 * consumerInvoker.invoke(consumer, _, _)
+		1 * consumerInvoker.invoke(completionConsumer, _, event)
+		1 * registration.cancel()
+		0 * errorConsumer.accept(_)
+	}
+
+	def "An exception during event routing causes the errorConsumer to be invoked"() {
+		def filter = new PassThroughFilter()
+		def consumerInvoker = Mock(ConsumerInvoker)
+		def completionConsumer = Mock(Consumer)
+		def errorConsumer = Mock(Consumer)
+		def consumer = Mock(Consumer)
+		def event = new Event("data")
+
+		given: "A consumer filtering event router"
+		def eventRouter = new ConsumerFilteringEventRouter(filter, consumerInvoker)
+		Registration registration = Mock(Registration)
+
+		when: "event routing triggers an exception"
+		registration.getObject() >> consumer
+		registration.getSelector() >> Mock(Selector)
+
+		consumerInvoker.invoke(_, _, _) >> { throw new Exception("failure") }
+
+		eventRouter.route("key", event, [registration], completionConsumer, errorConsumer)
+
+		then: "the error consumer is invoked"
+		2 * errorConsumer.accept(_)
+		0 * consumerInvoker.invoke(completionConsumer, _, event)
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/dispatch/DispatcherSpec.groovy b/reactor-core/src/test/groovy/reactor/dispatch/DispatcherSpec.groovy
new file mode 100644
index 0000000..ae59330
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/dispatch/DispatcherSpec.groovy
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package reactor.dispatch
+
+import com.lmax.disruptor.BlockingWaitStrategy
+import com.lmax.disruptor.dsl.ProducerType
+import reactor.core.Environment
+import reactor.core.Reactor
+import reactor.core.spec.Reactors
+import reactor.event.Event
+import reactor.event.dispatch.RingBufferDispatcher
+import reactor.event.dispatch.SynchronousDispatcher
+import reactor.event.dispatch.ThreadPoolExecutorDispatcher
+import reactor.event.dispatch.WorkQueueDispatcher
+import reactor.event.registry.CachingRegistry
+import reactor.event.routing.ArgumentConvertingConsumerInvoker
+import reactor.event.routing.ConsumerFilteringEventRouter
+import reactor.filter.PassThroughFilter
+import reactor.function.Consumer
+import reactor.function.support.Boundary
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import static reactor.GroovyTestUtils.$
+import static reactor.GroovyTestUtils.consumer
+import static reactor.event.selector.Selectors.T
+
+/**
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+class DispatcherSpec extends Specification {
+
+	def "Dispatcher executes tasks in correct thread"() {
+
+		given:
+			def sameThread = new SynchronousDispatcher()
+			def diffThread = new ThreadPoolExecutorDispatcher(1, 128)
+			def currentThread = Thread.currentThread()
+			Thread taskThread = null
+			def registry = new CachingRegistry<Consumer<Event>>()
+			def eventRouter = new ConsumerFilteringEventRouter(
+					new PassThroughFilter(), new ArgumentConvertingConsumerInvoker())
+			def sel = $('test')
+			registry.register(sel, consumer {
+				taskThread = Thread.currentThread()
+			})
+
+		when:
+			"a task is submitted"
+			sameThread.dispatch('test', Event.wrap('Hello World!'), registry, null, eventRouter, null)
+
+		then:
+			"the task thread should be the current thread"
+			currentThread == taskThread
+
+		when:
+			"a task is submitted to the thread pool dispatcher"
+			def latch = new CountDownLatch(1)
+			diffThread.dispatch('test', Event.wrap('Hello World!'), registry, null, eventRouter, { Event<String> ev -> latch.countDown() } as Consumer<Event<String>>)
+
+			latch.await(5, TimeUnit.SECONDS) // Wait for task to execute
+
+		then:
+			"the task thread should be different when the current thread"
+			taskThread != currentThread
+			//!diffThread.shutdown()
+
+	}
+
+	def "Dispatcher thread can be reused"() {
+
+		given:
+			"ring buffer reactor"
+			def env = new Environment()
+			def r = Reactors.reactor().env(env).dispatcher("ringBuffer").get()
+			def latch = new CountDownLatch(2)
+
+		when:
+			"listen for recursive event"
+			r.on($('test'), consumer { int i ->
+				if (i < 2) {
+					latch.countDown()
+					r.notify('test', Event.wrap(++i))
+				}
+			})
+
+		and:
+			"call the reactor"
+			r.notify('test', Event.wrap(0))
+
+		then:
+			"a task is submitted to the thread pool dispatcher"
+			latch.await(5, TimeUnit.SECONDS) // Wait for task to execute
+	}
+
+	def "Dispatchers can be shutdown awaiting tasks to complete"() {
+
+		given:
+			"a Reactor with a ThreadPoolExecutorDispatcher"
+			def env = new Environment()
+			def r = Reactors.reactor().
+					env(env).
+					dispatcher(Environment.THREAD_POOL).
+					get()
+			long start = System.currentTimeMillis()
+			def hello = ""
+			r.on($("pause"), { Event<String> ev ->
+				hello = ev.data
+				Thread.sleep(1000)
+			} as Consumer<Event<?>>)
+
+		when:
+			"the Dispatcher is shutdown and tasks are awaited"
+			r.notify("pause", Event.wrap("Hello World!"))
+			def success = r.dispatcher.awaitAndShutdown(5, TimeUnit.SECONDS)
+			long end = System.currentTimeMillis()
+
+		then:
+			"the Consumer was run, this thread was blocked, and the Dispatcher is shut down"
+			hello == "Hello World!"
+			success
+			(end - start) >= 1000
+
+	}
+
+	def "RingBufferDispatcher doesn't deadlock on thrown Exception"() {
+
+		given:
+			def b = new Boundary()
+			def dispatcher = new RingBufferDispatcher("rb", 8, null, ProducerType.MULTI, new BlockingWaitStrategy())
+			def r = new Reactor(dispatcher)
+
+		when:
+			r.on(T(Throwable), b.bind({ ev ->
+			} as Consumer<Event<Throwable>>, 16))
+			r.on($("test"), { ev ->
+				sleep(100)
+				1 / 0
+			} as Consumer<Event<String>>)
+			16.times {
+				r.notify "test", Event.wrap("test")
+			}
+
+		then:
+			b.await(5, TimeUnit.SECONDS)
+
+	}
+
+	def "RingBufferDispatcher executes tasks in correct thread"() {
+
+		given:
+			def dispatcher = new RingBufferDispatcher("rb", 8, null, ProducerType.MULTI, new BlockingWaitStrategy())
+			def t1 = Thread.currentThread()
+			def t2 = Thread.currentThread()
+
+		when:
+			dispatcher.execute({ t2 = Thread.currentThread() })
+			Thread.sleep(500)
+
+		then:
+			t1 != t2
+
+	}
+
+	def "WorkQueueDispatcher executes tasks in correct thread"() {
+
+		given:
+			def dispatcher = new WorkQueueDispatcher("rb", 8, 1024, null, ProducerType.MULTI, new BlockingWaitStrategy())
+			def t1 = Thread.currentThread()
+			def t2 = Thread.currentThread()
+
+		when:
+			dispatcher.execute({ t2 = Thread.currentThread() })
+			Thread.sleep(500)
+
+		then:
+			t1 != t2
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/event/HeadersSpec.groovy b/reactor-core/src/test/groovy/reactor/event/HeadersSpec.groovy
new file mode 100644
index 0000000..61ee61e
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/event/HeadersSpec.groovy
@@ -0,0 +1,148 @@
+package reactor.event
+
+import reactor.event.Event.Headers
+import spock.lang.Specification
+
+class HeadersSpec extends Specification {
+
+	def 'Header names are case-insensitive'() {
+		when: 'A header is named alpha'
+		Headers headers = new Headers()
+		headers.set('alpha', 'a')
+
+		then: 'It can be retrieved using aLpHa'
+		'a' == headers.get('aLpHa')
+	}
+
+	def 'Headers can be made read-only'() {
+		given: 'Some read-only headers'
+		Headers headers = new Headers().readOnly()
+
+		when: 'They are modified'
+		headers.set('a', 'alpha')
+
+		then: 'Then it fails with an UnsupportedOperationException'
+		thrown(UnsupportedOperationException)
+	}
+
+	def 'Headers can be created from a Map'() {
+		given: 'A map'
+		Map map = ['a':'alpha', 'b':'bravo']
+
+		when: 'Headers are created from the map'
+		Headers headers = new Headers(map)
+
+		then: 'They contain the contents of the map'
+		'alpha' == headers.get('a')
+		'bravo' == headers.get('b')
+	}
+
+	def 'Headers can return its contents as an unmodifiable map'() {
+		given: 'A Headers instance containing some headers'
+		Headers headers = new Headers()
+		headers.set('a', 'alpha')
+		headers.set('b', 'bravo')
+
+		when: 'A map is created from the Headers'
+		Map map = headers.asMap()
+
+		then: 'The map contains the headers'
+		'alpha' == map.get('a')
+		'bravo' == map.get('b')
+
+		when: 'The map is modified'
+		map.put('c', 'charlie')
+
+		then: 'An UnsupportedOperationException is thrown'
+		thrown(UnsupportedOperationException)
+	}
+
+	def 'Headers provides an iterator over its contents'() {
+		given: 'A headers instance containing some headers'
+		Headers headers = new Headers()
+		headers.set('a', 'alpha')
+		headers.set('b', 'bravo')
+		Iterator iterator = headers.iterator()
+
+		when: 'An item is removed using the iterator'
+		iterator.remove()
+
+		then: 'An UnsupportedOperationException is thrown'
+		thrown(UnsupportedOperationException)
+
+		when: 'The iterator is used to retrieve the headers'
+		Map map = [:]
+		iterator.each { header ->
+			map.put(header.t1, header.t2)
+		}
+
+		then: 'The iterator provided access to the headers'
+		map.size() == 2
+		'alpha' == map.get('a')
+		'bravo' == map.get('b')
+	}
+
+	def 'Setting a header with a null value removes the header'() {
+		given: 'A Headers instance containing a header'
+		Headers headers = new Headers()
+		headers.set('a', 'alpha')
+
+		when: "That header's value is set to null"
+		headers.set('a', null)
+
+		then: 'It is removed from the headers'
+		!headers.contains('a')
+	}
+
+	def 'The origin can be set using a UUID'() {
+		given: 'A Headers instance and a UUID'
+		Headers headers = new Headers()
+		UUID uuid = UUID.randomUUID()
+
+		when: 'The origin is set with the UUID'
+		headers.setOrigin(uuid)
+
+		then: 'The origin is the string form of the UUID'
+		uuid.toString() == headers.getOrigin()
+	}
+
+	def 'The origin can be set using a String'() {
+		given: 'A Headers instance and an origin string'
+		Headers headers = new Headers()
+		String uuid = '1234567890'
+
+		when: 'The origin is set'
+		headers.setOrigin(uuid)
+
+		then: 'The origin can be retrieved'
+		uuid == headers.getOrigin()
+	}
+
+	def 'Setting the origin with a null UUID removes the origin'() {
+		given: 'A Headers instance with an origin'
+		Headers headers = new Headers()
+		headers.setOrigin('1234567890')
+
+		when: 'The origin is set to a null UUID'
+		headers.setOrigin((UUID)null)
+
+		then: 'The origin has been removed'
+		headers.getOrigin() == null
+	}
+
+	def 'The contents of a Map can be added to a Headers instance'() {
+		given: 'A Headers instance containg a header'
+		Headers headers = new Headers()
+		headers.set('a', 'alpha')
+		headers.set('b', 'bravo')
+
+		when: 'A map of headers is set'
+		headers.setAll('a':'aardvark', 'b':null, 'c':'charlie')
+
+		then: 'The headers have been updated'
+		'aardvark' == headers.get('a')
+		'charlie' == headers.get('c')
+		!headers.contains('b')
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/event/SelectorSpec.groovy b/reactor-core/src/test/groovy/reactor/event/SelectorSpec.groovy
new file mode 100644
index 0000000..5d85195
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/event/SelectorSpec.groovy
@@ -0,0 +1,262 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event
+
+import reactor.core.spec.Reactors
+import reactor.event.selector.MatchAllSelector
+import reactor.event.selector.SetMembershipSelector
+import reactor.event.selector.UriSelector
+import reactor.function.Functions
+import spock.lang.Specification
+
+import static org.hamcrest.CoreMatchers.*
+import static org.hamcrest.MatcherAssert.assertThat
+import static reactor.GroovyTestUtils.$
+import static reactor.GroovyTestUtils.consumer
+import static reactor.event.selector.Selectors.*
+/**
+ * @author Jon Brisbin
+ * @author Andy Wilkinson
+ * @author Stephane Maldini
+ */
+class SelectorSpec extends Specification {
+
+	def "Selectors work with Strings"() {
+
+		when:
+			"a selector backed by a String is defined"
+			def selector = $("test")
+
+		then:
+			"it matches the string"
+			selector.matches "test"
+	}
+
+	def "Selectors work with primitives"() {
+
+		when:
+			"a selector backed by a primitve is defined"
+			def selector = $(1L)
+
+		then:
+			"it matches the primitive"
+			selector.matches 1L
+	}
+
+	def "Class selectors match on isAssignableFrom"() {
+
+		when:
+			"a selector based on Class is defined"
+			def clz1 = T(Throwable)
+			def clz2 = T(IllegalArgumentException)
+
+		then:
+			"it matches the class and its sub types"
+			clz1.matches Throwable
+			clz1.matches IllegalArgumentException
+	}
+
+	def "Regex selectors match on expressions"() {
+
+		when:
+			"A selector based on a regular expression and a matching key are defined"
+			def sel1 = R("test([0-9]+)")
+			def key = "test1"
+
+		then:
+			"they match"
+			sel1.matches "test1"
+
+		when:
+			"a key the does not fit the pattern is provided"
+			def key2 = "test-should-not-match"
+
+		then:
+			"it does not match"
+			!sel1.matches(key2)
+
+	}
+
+	def "Selectors can be matched on URI path"() {
+
+		given:
+			"A UriPathSelector"
+			def sel1 = U("/path/**/{resource}")
+			def key = "/path/to/some/resourceId"
+			def r = Reactors.reactor().synchronousDispatcher().get()
+			def resourceId = ""
+			r.on(sel1, consumer { Event<String> ev ->
+				resourceId = ev.headers["resource"]
+			})
+
+		when:
+			"The selector is matched"
+			r.notify key, Event.wrap("")
+
+		then:
+			"The resourceId has been set when the headers"
+			resourceId == 'resourceId'
+
+	}
+
+	def "Selectors can be matched on URIs"() {
+
+		given:
+			"A UriSelector"
+			def sel1 = new UriSelector("http://user:pwd@host:80/path/segment?param=value#fragment")
+			def sel2 = new UriSelector("http://*:80/path/segment#fragment")
+			def sel3 = new UriSelector("http://user:ENCODEDPWD@*:3000/path/segment#fragment")
+			def r = Reactors.reactor().synchronousDispatcher().get()
+			def vals = [:]
+			r.on(sel1, consumer { Event<String> ev ->
+				vals = ev.headers
+			})
+			r.on(sel2, consumer { Event<String> ev ->
+				vals["wildcard"] = true
+			})
+			r.on(sel3, consumer { Event<String> ev ->
+				// shouldn't be matched
+				vals = [:]
+			})
+
+		when:
+			"The Selector is matched"
+			r.notify("http://user:pwd@host:80/path/segment?param=value#fragment", Event.wrap(""))
+
+		then:
+			"The URI has been matched and data extracted"
+			vals["scheme"] == "http"
+			vals["userInfo"] == "user:pwd"
+			vals["host"] == "host"
+			vals["port"] == "80"
+			vals["path"] == "/path/segment"
+			vals["fragment"] == "fragment"
+			vals["query"] == "param=value"
+			vals["param"] == "value"
+			vals["wildcard"]
+
+	}
+
+    def "Match-All selector is available"() {
+
+        given:
+            "A MatchAllSelector"
+            def sel = new MatchAllSelector()
+
+        when:
+            "The selector is matched"
+
+        then:
+            sel.matches "a string"
+            sel.matches 1L
+            sel.matches true
+            sel.matches false
+            sel.matches(new Date())
+            sel.matches(new Object())
+    }
+
+	def "Set membership selector is available"() {
+
+		given:
+		"A SetMembershipSelector"
+		def coll = ["a", "b", "c"] as Set<String>
+		def sel = new SetMembershipSelector(coll)
+
+		when:
+		"The selector is matched"
+
+		then:
+		sel.matches "a"
+		sel.matches "b"
+		sel.matches "c"
+		!sel.matches("d")
+		!sel.matches([1])
+		!sel.matches(1)
+		!sel.matches(1.0)
+	}
+
+	def "Consumers can be called using round-robin routing"() {
+
+		given:
+			"A Reactor using round-robin routing and a set of consumers assigned to the same selector"
+			def r = Reactors.reactor().synchronousDispatcher().roundRobinEventRouting().get()
+			def called = []
+			def a1 = {
+				called << 1
+			}
+			def a2 = {
+				called << 2
+			}
+			def a3 = {
+				called << 3
+			}
+			def a4 = {
+				called << 4
+			}
+			r.on($('key'), Functions.consumer(a1))
+			r.on($('key'), Functions.consumer(a2))
+			r.on($('key'), Functions.consumer(a3))
+			r.on($('key'), Functions.consumer(a4))
+
+		when:
+			"events are triggered"
+			(1..4).each {
+				r.notify('key', Event.wrap("Hello World!"))
+			}
+
+		then:
+			"all consumers should have been called once"
+			assertThat(called, hasItems(1, 2, 3, 4))
+	}
+
+	def "Consumers can be routed to randomly"() {
+
+		given:
+			"A Reactor using random routing and a set of consumers assigned to the same selector"
+
+			def r = Reactors.reactor().synchronousDispatcher().randomEventRouting().get()
+			def called = []
+			def a1 = {
+				called << 1
+			}
+			def a2 = {
+				called << 2
+			}
+			def a3 = {
+				called << 3
+			}
+			def a4 = {
+				called << 4
+			}
+			r.on($('test'),Functions.consumer(a1))
+			r.on($('test'),Functions.consumer(a2))
+			r.on($('test'),Functions.consumer(a3))
+			r.on($('test'),Functions.consumer(a4))
+
+		when:
+			"events are triggered"
+
+			(1..4).each {
+				r.notify('test', Event.wrap("Hello World!"))
+			}
+
+		then:
+			"random selection of consumers have been called"
+			assertThat(called, anyOf(hasItem(1), hasItem(2), hasItem(3), hasItem(4)))
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/filter/FirstFilterSpec.groovy b/reactor-core/src/test/groovy/reactor/filter/FirstFilterSpec.groovy
new file mode 100644
index 0000000..4050387
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/filter/FirstFilterSpec.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.filter
+
+import spock.lang.Specification
+
+class FirstFilterSpec extends Specification {
+
+	def "When items are filtered a single randomly selected item is returned"() {
+		given: "A random filter"
+		def filter = new FirstFilter()
+
+		when: "items are filtered"
+		def items = ['a', 'b', 'c']
+		def filteredItems = filter.filter items, null
+
+		then: "a single item is returned"
+		filteredItems.size() == 1
+		items.contains filteredItems[0]
+	}
+
+	def "When null items are filtered an IllegalStateException is thrown"() {
+		given: "A random filter"
+		def filter = new FirstFilter()
+
+		when: "null items are filtered"
+		filter.filter null, null
+
+		then: "an IllegalArgumentException was thrown"
+		thrown(IllegalArgumentException)
+	}
+
+	def "When an empty list of items are filtered, an empty list is returned"() {
+		given: "A random filter"
+		def filter = new FirstFilter()
+
+		when: "an empty list of items is filtered"
+		def filteredItems = filter.filter([], null)
+
+		then: "an empty list is returned"
+		filteredItems.empty
+	}
+}
diff --git a/reactor-core/src/test/groovy/reactor/filter/PassThroughFilterSpec.groovy b/reactor-core/src/test/groovy/reactor/filter/PassThroughFilterSpec.groovy
new file mode 100644
index 0000000..4181758
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/filter/PassThroughFilterSpec.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+
+
+package reactor.filter
+
+import spock.lang.Specification
+
+class PassThroughFilterSpec extends Specification {
+
+	def "When items are filtered they are returned as is"() {
+		given: "A pass-through filter"
+		def filter = new PassThroughFilter()
+
+		when: "items are filtered"
+		def items = ['a', 'b', 'c']
+		def filteredItems = filter.filter items, null
+
+		then: "they are returned as-is"
+		items == filteredItems
+	}
+
+	def "When null items are filtered an IllegalStateException is thrown"() {
+		given: "A pass-through filter"
+		def filter = new PassThroughFilter()
+
+		when: "null items are filtered"
+		filter.filter null, null
+
+		then: "an IllegalArgumentException is thrown"
+		thrown(IllegalArgumentException)
+	}
+}
diff --git a/reactor-core/src/test/groovy/reactor/filter/RandomFilterSpec.groovy b/reactor-core/src/test/groovy/reactor/filter/RandomFilterSpec.groovy
new file mode 100644
index 0000000..ac226fe
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/filter/RandomFilterSpec.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+
+
+package reactor.filter
+
+import spock.lang.Specification
+
+class RandomFilterSpec extends Specification {
+
+	def "When items are filtered a single randomly selected item is returned"() {
+		given: "A random filter"
+		def filter = new RandomFilter()
+
+		when: "items are filtered"
+		def items = ['a', 'b', 'c']
+		def filteredItems = filter.filter items, null
+
+		then: "a single item is returned"
+		filteredItems.size() == 1
+		items.contains filteredItems[0]
+	}
+
+	def "When null items are filtered an IllegalStateException is thrown"() {
+		given: "A random filter"
+		def filter = new RandomFilter()
+
+		when: "null items are filtered"
+		filter.filter null, null
+
+		then: "an IllegalArgumentException was thrown"
+		thrown(IllegalArgumentException)
+	}
+
+	def "When an empty list of items are filtered, an empty list is returned"() {
+		given: "A random filter"
+		def filter = new RandomFilter()
+
+		when: "an empty list of items is filtered"
+		def filteredItems = filter.filter([], null)
+
+		then: "an empty list is returned"
+		filteredItems.empty
+	}
+}
diff --git a/reactor-core/src/test/groovy/reactor/filter/RoundRobinFilterSpec.groovy b/reactor-core/src/test/groovy/reactor/filter/RoundRobinFilterSpec.groovy
new file mode 100644
index 0000000..81fdf8e
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/filter/RoundRobinFilterSpec.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+
+
+package reactor.filter
+
+import spock.lang.Specification
+
+class RoundRobinFilterSpec extends Specification {
+
+	def "When items are filtered multiple times a single item, selected using a round-robin algorithm, is returned"() {
+		given: "A round robin filter and a list of three items"
+		def filter = new RoundRobinFilter()
+	  def items = ['a', 'b', 'c']
+
+		when: "items are filtered"
+		def filteredItems = filter.filter items, "key"
+
+		then: "the first item is returned"
+		filteredItems.size() == 1
+		filteredItems[0] == 'a'
+
+		when: "items are filtered a second time"
+		filteredItems = filter.filter items, "key"
+
+		then: "the second item is returned"
+		filteredItems.size() == 1
+		filteredItems[0] == 'b'
+
+		when: "items are filtered a third time"
+		filteredItems = filter.filter items, "key"
+
+		then: "the third item is returned"
+		filteredItems.size() == 1
+		filteredItems[0] == 'c'
+
+		when: "items are filtered a fourth time"
+		filteredItems = filter.filter items, "key"
+
+		then: "the first item is returned"
+		filteredItems.size() == 1
+		filteredItems[0] == 'a'
+
+		when: "items are filtered with a different key"
+		filteredItems = filter.filter items, "new-key"
+
+		then: "the first item is returned"
+	}
+
+	def "When null items are filtered an IllegalStateException is thrown"() {
+		given: "A round robin filter"
+		def filter = new RoundRobinFilter()
+
+		when: "null items are filtered"
+		filter.filter null, null
+
+		then: "an IllegalArgumentException was thrown"
+		thrown(IllegalArgumentException)
+	}
+
+	def "When a null key is provided an IllegalStateException is thrown"() {
+		given: "A round robin filter"
+		def filter = new RoundRobinFilter()
+
+		when: "a null key is provided"
+		filter.filter(['a'], null)
+
+		then: "an IllegalArgumentException was thrown"
+		thrown(IllegalArgumentException)
+	}
+
+	def "When an empty list of items are filtered, an empty list is returned"() {
+		given: "A random filter"
+		def filter = new RandomFilter()
+
+		when: "an empty list of items is filtered"
+		def filteredItems = filter.filter([], null)
+
+		then: "an empty list is returned"
+		filteredItems.empty
+	}
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/BufferSpec.groovy b/reactor-core/src/test/groovy/reactor/io/BufferSpec.groovy
new file mode 100644
index 0000000..1ab0271
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/BufferSpec.groovy
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.io
+
+import spock.lang.Specification
+
+import java.nio.BufferOverflowException
+import java.nio.ByteBuffer
+
+/**
+ * @author Jon Brisbin
+ */
+class BufferSpec extends Specification {
+
+	def "A Buffer can be created from a String"() {
+		when: "a Buffer is created from a String"
+		def buff = Buffer.wrap("Hello World!")
+
+		then: "the Buffer contains the String"
+		buff.asString() == "Hello World!"
+	}
+
+	def "A fixed-length Buffer can be created from a String"() {
+		given: "a fixed-length Buffer is created"
+		def buff = Buffer.wrap("Hello", true)
+
+		when: "an attempt is made to append data to the Buffer"
+		buff.append(" World!")
+
+		then: "an exception is thrown"
+		thrown(BufferOverflowException)
+	}
+
+	def "A Buffer prepends a Buffer to an existing Buffer"() {
+		given: "an full Buffer"
+		def buff = Buffer.wrap("World!", false)
+
+		when: "another Buffer is prepended"
+		buff.prepend(Buffer.wrap("Hello "))
+
+		then: "the Buffer was prepended"
+		buff.asString() == "Hello World!"
+	}
+
+	def "A Buffer prepends Strings to an existing Buffer"() {
+		given: "an full Buffer"
+		def buff = Buffer.wrap("World!", false)
+
+		when: "a String is prepended"
+		buff.prepend("Hello ")
+
+		then: "the String was prepended"
+		buff.asString() == "Hello World!"
+	}
+
+	def "A Buffer prepends primitives to an existing Buffer"() {
+		given: "a Buffer with data"
+		def buff = Buffer.wrap("5", false)
+
+		when: "a byte is prepended"
+		buff.prepend((byte) 52)
+
+		then: "the byte was prepended"
+		buff.asString() == "45"
+
+		when: "a char is prepended"
+		buff.prepend((char) 51)
+
+		then: "the char was prepended"
+		buff.readChar() == '3'
+
+		when: "an int is prepended"
+		buff.prepend(2)
+
+		then: "the int was prepended"
+		buff.readInt() == 2
+
+		when: "a long is prepended"
+		buff.prepend(1L)
+
+		then: "the long was prepended"
+		buff.readLong() == 1L
+	}
+
+	def "A Buffer reads and writes Buffers"() {
+		given: "an empty Buffer and a full Buffer"
+		def buff = new Buffer()
+		def fullBuff = Buffer.wrap("Hello World!")
+
+		when: "a Buffer is appended"
+		buff.append(fullBuff)
+
+		then: "the Buffer was added"
+		buff.position() == 12
+		buff.flip().asString() == "Hello World!"
+	}
+
+	def "A Buffer reads and writes Strings"() {
+		given: "an empty Buffer"
+		def buff = new Buffer()
+
+		when: "a String is appended"
+		buff.append("Hello World!")
+
+		then: "the String was added"
+		buff.position() == 12
+		buff.flip().asString() == "Hello World!"
+	}
+
+	def "A Buffer reads and writes primitives"() {
+		given: "an empty Buffer"
+		def buff = new Buffer()
+
+		when: "a byte is appended"
+		buff.append((byte) 1)
+
+		then: "the byte was added"
+		buff.position() == 1
+		buff.flip().read() == 1
+
+		when: "a char is appended"
+		buff.clear()
+		buff.append((char) 1)
+
+		then: "the char was added"
+		buff.position() == 2
+		buff.flip().readChar() == (char) 1
+
+		when: "an int is appended"
+		buff.clear()
+		buff.append(1)
+
+		then: "the int was added"
+		buff.position() == 4
+		buff.flip().readInt() == 1
+
+		when: "a long is appended"
+		buff.clear()
+		buff.append(1L)
+
+		then: "the long was added"
+		buff.position() == 8
+		buff.flip().readLong() == 1L
+	}
+
+	def "A Buffer provides position, limit, capacity, and remaining"() {
+		given: "a full Buffer"
+		def buff = Buffer.wrap("Hello World!")
+
+		when: "limit is checked"
+		def limit = buff.limit()
+
+		then: "a limit is provided"
+		limit == 12
+
+		when: "capacity is checked"
+		def cap = buff.capacity()
+
+		then: "a capacity is provided"
+		cap == 12
+
+		when: "position is checked"
+		def pos = buff.position()
+
+		then: "a position is provided"
+		pos == 0
+	}
+
+	def "A Buffer can have first and last positions read"() {
+		given: "a full Buffer"
+		def buff = Buffer.wrap("Hello World!")
+
+		when: "the first byte is checked"
+		def first = buff.first()
+
+		then: "the first byte is an H"
+		first == (byte) 72
+
+		when: "the last byte is checked"
+		def last = buff.last()
+
+		then: "the last byte is an !"
+		last == (byte) 33
+	}
+
+	def "A Buffer provides an iterator over each byte"() {
+		given: "a full Buffer"
+		def buff = Buffer.wrap("Hello World!")
+		def count = 0
+
+		when: "the bytes are iterated over"
+		buff.each { b ->
+			count++
+		}
+
+		then: "the count should be 12"
+		count == 12
+	}
+
+	def "A Buffer can be efficiently substringed"() {
+		given: "a full Buffer"
+		def buff = Buffer.wrap("Hello World!")
+
+		when: "a substring is extracted"
+		def substr = buff.substring(6, 11)
+
+		then: "the substring was extracted"
+		substr == "World"
+	}
+
+	def "A Buffer is also a ReadableByteChannel and WritableByteChannel"() {
+		given: "an empty Buffer as a WritableByteChannel"
+		def buff = new Buffer(12, true)
+
+		when: "a ByteBuffer is written into the Buffer"
+		def bb = ByteBuffer.wrap("Hello World!".bytes)
+		buff.write(bb)
+
+		then: "the Buffer had data written to it"
+		buff.flip().asString() == "Hello World!"
+
+		when: "a ByteBuffer is read from the Buffer"
+		bb = ByteBuffer.allocate(5)
+		buff.read(bb)
+
+		then: "the ByteBuffer has data in it"
+		bb.position() == 5
+		buff.position() == 5
+		bb.flip().get() == (byte) 72
+	}
+
+	def "A Buffer can be split into segments based on a delimiter"() {
+		given: "a full Buffer"
+		def buff = Buffer.wrap("Hello World!\nHello World!\nHello World!")
+
+		when: "the Buffer is split"
+		def parts = buff.split(10)
+
+		then: "there are only 2 parts"
+		parts.size() == 2
+	}
+
+	def "Splitting a single-segment buffer yields a single part with the expected contents"() {
+		given: "A buffer with a single segment"
+		def buff = Buffer.wrap("Hello World!\n")
+
+		when: "the buffer is split"
+		def parts = buff.split((int) '\n')
+
+		then: "there is a single part"
+		parts.size() == 1
+		def strings = []
+		parts.each { part -> strings << new String(part.get().asBytes()) }
+		strings == ['Hello World!\n']
+	}
+
+	def "Splitting a two-segment buffer yields two parts with the expected contents"() {
+		given: "A buffer with two segments"
+		def buff = Buffer.wrap("Hello World!\nHello World!\n")
+
+		when: "the buffer is split"
+		def parts = buff.split((int) '\n')
+
+		then: "there are two parts"
+		parts.size() == 2
+		def strings = []
+		parts.each { part -> strings << new String(part.get().asBytes()) }
+		strings == ['Hello World!\n', 'Hello World!\n']
+	}
+
+	def "A buffer can be split on a delimiter and the delimiter can be stripped from each segment"() {
+		given: "A buffer with three segments"
+		def buff = Buffer.wrap("One\nTwo\nThree\n")
+
+		when: "the buffer is split on the delimiter and the delimiter is stripped"
+		def parts = buff.split(10, true)
+
+		then: "three parts with the expected contents are produced"
+		def strings = []
+		parts.each { part -> strings << part.get().asString() }
+		strings.size() == 3
+		strings == ['One', 'Two', 'Three']
+	}
+
+	def "A buffer can be split on a delimiter of multiple byte length"() {
+		given:
+			"A buffer with three segments"
+			def buff = Buffer.wrap "One\r\nTwo\r\nThree\r\n"
+			def delim = Buffer.wrap "\r\n"
+
+		when:
+			"the buffer is split on the delimiter and the delimiter is stripped"
+			def parts = buff.split(delim, true)
+
+		then:
+			"three parts with the expected contents are produced"
+			def strings = []
+			parts.each { part -> strings << part.get().asString() }
+			strings.size() == 3
+			strings == ['One', 'Two', 'Three']
+	}
+
+	def "A Buffer can be sliced into segments"() {
+		given: "a syslog message, buffered"
+		def buff = Buffer.wrap("<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8\n")
+
+		when: "positions are assigned and the buffer is sliced"
+		def positions = [1, 3, 4, 19, 20, 29, 30] as int[]
+		def slices = buff.slice(positions)
+
+		then: "the buffer is sliced"
+		slices[0].get().asString() == "34"
+		slices[1].get().asString() == "Oct 11 22:14:15"
+		slices[2].get().asString() == "mymachine"
+		slices[3].get().asString() == "su: 'su root' failed for lonvick on /dev/pts/8\n"
+	}
+
+	def "A Buffer rejects an attempt to rewind by a negative number of bytes"() {
+		given: "A buffer"
+		def buffer = Buffer.wrap("some data")
+
+		when: "The buffer is rewound by a negative number of bytes"
+		buffer.rewind(-5)
+
+		then: "An IllegalArgumentException is thrown"
+		thrown(IllegalArgumentException)
+	}
+
+	def "A Buffer rejects an attempt to skip a negative number of bytes"() {
+		given: "A buffer"
+		def buffer = Buffer.wrap("some data")
+
+		when: "The buffer is asked to skip a negative number of bytes"
+		buffer.skip(-5)
+
+		then: "An IllegalArgumentException is thrown"
+		thrown(IllegalArgumentException)
+	}
+
+	def "An IllegalArgumentException is thrown if a buffer is asked to skip beyond its end"() {
+		given: "A buffer"
+		def buffer = Buffer.wrap("some data")
+
+		when: "The buffer is asked to skip beyond its end"
+		buffer.skip(100)
+
+		then: "An IllegalArgumentException is thrown"
+		thrown(IllegalArgumentException)
+	}
+
+	def "An IllegalArgumentException is thrown if a buffer is asked to rewind beyond its beginning"() {
+		given: "A buffer"
+		def buffer = Buffer.wrap("some data")
+
+		when: "The buffer is asked to rewind beyond its beginning"
+		buffer.rewind(100)
+
+		then: "An IllegalArgumentException is thrown"
+		thrown(IllegalArgumentException)
+	}
+
+	def "A Buffer can be duplicated"() {
+		given: "A Buffer"
+		def buffer = new Buffer(128, true).append("Hello World!").flip()
+
+		when: "the Buffer is duplicated"
+		def dup = buffer.duplicate()
+
+		then: "a new Buffer is created on a duplicate"
+		dup.capacity() == 128
+		dup.asString() == "Hello World!"
+	}
+
+	def "A Buffer can be copied"() {
+		given: "A Buffer"
+		def buffer = new Buffer(128, true).append("Hello World!").flip()
+
+		when: "the Buffer is copied"
+		def copy = buffer.copy()
+
+		then: "a new Buffer is created on a copy"
+		copy.capacity() == Buffer.SMALL_BUFFER_SIZE
+		copy.asString() == "Hello World!"
+	}
+
+	def "A Buffer can be searched"() {
+		given: "A Buffer"
+			def buffer = Buffer.wrap("Hello World!")
+
+		when: "the Buffer is searched"
+		def pos = buffer.indexOf((byte)0x21)
+
+		then: "the char is found"
+		pos == 12
+
+		when: "the Buffer is searched within a range"
+		pos = buffer.indexOf((byte)0x21, 0, 11)
+
+		then: "the char is not found"
+		pos == -1
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/encoding/StandardCodecsSpec.groovy b/reactor-core/src/test/groovy/reactor/io/encoding/StandardCodecsSpec.groovy
new file mode 100644
index 0000000..1d7a341
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/encoding/StandardCodecsSpec.groovy
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.io.encoding
+
+import reactor.function.Consumer
+import reactor.io.Buffer
+import spock.lang.Specification
+
+/**
+ * Tests to cover the basic, built-in Codecs.
+ * @author Jon Brisbin
+ */
+class StandardCodecsSpec extends Specification {
+
+	def "StringCodec can encode and decode to Strings"() {
+		given: "String data"
+		def codec = new StringCodec()
+		def data = Buffer.wrap("Hello World!")
+		def hello = ""
+
+		when: "the Buffer is decoded"
+		hello = codec.decoder(null).apply(data)
+
+		then: "the String was decoded"
+		hello == "Hello World!"
+
+		when: "the String is encoded"
+		data = codec.encoder().apply(hello)
+
+		then: "the String was encoded"
+		data instanceof Buffer
+		data.asString() == "Hello World!"
+	}
+
+	def "DelimitedCodec can encode and decode delimited lines"() {
+		given: "delimited data"
+		def codec = new DelimitedCodec<String, String>(false, StandardCodecs.STRING_CODEC)
+		def data = Buffer.wrap("Hello World!\nHello World!\nHello World!\n")
+		def hellos = []
+
+		when: "data is decoded"
+		def decoder = codec.decoder({ String s ->
+			hellos << s
+		} as Consumer<String>)
+		decoder.apply(data)
+
+		then: "data was decoded"
+		hellos == ["Hello World!\n", "Hello World!\n", "Hello World!\n"]
+
+		when: "data is encoded"
+		def encoder = codec.encoder()
+		def hw1 = encoder.apply("Hello World!")
+		def hw2 = encoder.apply("Hello World!")
+		def hw3 = encoder.apply("Hello World!")
+		def buff = new Buffer().append(hw1, hw2, hw3).flip()
+
+		then: "data was encoded"
+		buff.asString() == data.flip().asString()
+	}
+
+	def "Once decoding has completed, the buffer's position and limit are at the end of the buffer"() {
+		given: "A decoder and a buffer of delimited data"
+		def codec = new DelimitedCodec<String, String>(false, StandardCodecs.STRING_CODEC)
+		def string = 'Hello World!\nHello World!\nHello World!\n'
+		def data = Buffer.wrap(string)
+
+		when: "data has been decoded"
+		def decoder = codec.decoder({} as Consumer<String>)
+		decoder.apply(data)
+
+		then: "the buffer's limit and position are at the end of the buffer"
+		data.limit() == string.length()
+		data.position() == string.length()
+	}
+
+	def "Once delimiter stripping decoding has completed, the buffer's position and limit are at the end of the buffer"() {
+		given: "A delimiter stripping decoder and a buffer of delimited data"
+		def codec = new DelimitedCodec<String, String>(true, StandardCodecs.STRING_CODEC)
+		def string = 'Hello World!\nHello World!\nHello World!\n'
+		def data = Buffer.wrap(string)
+
+		when: "data has been decoded"
+		def decoder = codec.decoder({} as Consumer<String>)
+		decoder.apply(data)
+
+		then: "the buffer's limit and position are at the end of the buffer"
+		data.limit() == string.length()
+		data.position() == string.length()
+	}
+
+	def "LengthFieldCodec can encode and decode length-prefixed items"() {
+		given: "length-prefixed data"
+		def codec = new LengthFieldCodec<String, String>(StandardCodecs.STRING_CODEC)
+		def data = new Buffer().append((int) 12).append("Hello World!").flip()
+
+		when: "the data is decoded"
+		def len = data.readInt()
+		def hello = data.asString()
+
+		then: "the data was decoded"
+		len == 12
+		hello == "Hello World!"
+
+		when: "the data is encoded"
+		data = codec.encoder().apply(hello)
+
+		then: "the data was encoded"
+		data.readInt() == 12
+		data.asString() == "Hello World!"
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/encoding/compress/CompressionCodecsSpec.groovy b/reactor-core/src/test/groovy/reactor/io/encoding/compress/CompressionCodecsSpec.groovy
new file mode 100644
index 0000000..48eef5e
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/encoding/compress/CompressionCodecsSpec.groovy
@@ -0,0 +1,52 @@
+package reactor.io.encoding.compress
+
+import reactor.io.Buffer
+import spock.lang.Specification
+
+import static reactor.io.encoding.StandardCodecs.PASS_THROUGH_CODEC
+
+/**
+ * @author Jon Brisbin
+ */
+class CompressionCodecsSpec extends Specification {
+
+	GzipCodec<Buffer, Buffer> gzip
+	SnappyCodec<Buffer, Buffer> snappy
+
+	def setup() {
+		gzip = new GzipCodec<Buffer, Buffer>(PASS_THROUGH_CODEC)
+		snappy = new SnappyCodec<Buffer, Buffer>(PASS_THROUGH_CODEC)
+	}
+
+	def "compression codecs preserve integrity of data"() {
+
+		given:
+			Buffer buffer
+
+		when: "an object is encoded with GZIP"
+			buffer = gzip.encoder().apply(Buffer.wrap("Hello World!"))
+
+		then: "the Buffer was encoded and compressed"
+			buffer.remaining() == 32
+
+		when: "an object is decoded with GZIP"
+			String hw = gzip.decoder(null).apply(buffer).asString()
+
+		then: "the Buffer was decoded and uncompressed"
+			hw == "Hello World!"
+
+		when: "an object is encoded with Snappy"
+			buffer = snappy.encoder().apply(Buffer.wrap("Hello World!"))
+
+		then: "the Buffer was encoded and compressed"
+			buffer.remaining() == 34
+
+		when: "an object is decoded with Snappy"
+			hw = snappy.decoder(null).apply(buffer).asString()
+
+		then: "the Buffer was decoded and uncompressed"
+			hw == "Hello World!"
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/encoding/json/JacksonJsonCodecSpec.groovy b/reactor-core/src/test/groovy/reactor/io/encoding/json/JacksonJsonCodecSpec.groovy
new file mode 100644
index 0000000..a215feb
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/encoding/json/JacksonJsonCodecSpec.groovy
@@ -0,0 +1,42 @@
+package reactor.io.encoding.json
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import reactor.io.Buffer
+import spock.lang.Specification
+
+/**
+ * @author Jon Brisbin
+ */
+class JacksonJsonCodecSpec extends Specification {
+
+	ObjectMapper mapper
+
+	def setup() {
+		mapper = new ObjectMapper()
+	}
+
+	def "serializes and deserializes objects properly"() {
+
+		given: "a Codec and a Buffer"
+			def codec = new JacksonJsonCodec<Person, Person>(mapper)
+			Buffer buffer
+
+		when: "an object is serialized"
+			buffer = codec.encoder().apply(new Person(name: "John Doe"))
+
+		then: "the object was serialized"
+			buffer.remaining() == 79
+
+		when: "an object is deserialized"
+			Person p = codec.decoder(null).apply(buffer)
+
+		then: "the object was deserialized"
+			p.name == "John Doe"
+
+	}
+
+	static class Person {
+		String name
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/encoding/json/JsonCodecSpec.groovy b/reactor-core/src/test/groovy/reactor/io/encoding/json/JsonCodecSpec.groovy
new file mode 100644
index 0000000..db24d1d
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/encoding/json/JsonCodecSpec.groovy
@@ -0,0 +1,41 @@
+package reactor.io.encoding.json
+
+import reactor.function.Consumer
+import reactor.function.Function
+import reactor.io.Buffer
+import spock.lang.Specification
+
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.node.ObjectNode
+
+class JsonCodecSpec extends Specification {
+
+	def "JSON can be decoded into a Map"() {
+		given: 'A JSON codec'
+		JsonCodec<Map<String, Object>, Object> codec = new JsonCodec<Map<String, Object>, Object>(Map);
+
+		when: 'The decoder is passed some JSON'
+		Map<String, Object> decoded;
+		Function<Buffer, Map<String, Object>> decoder = codec.decoder({ decoded = it} as Consumer<Map<String, Object>>)
+		decoder.apply(Buffer.wrap("{\"a\": \"alpha\"}"));
+
+		then: 'The decoded map has the expected entries'
+		decoded.size() == 1
+		decoded['a'] == 'alpha'
+	}
+
+	def "JSON can be decoded into a JsonNode"() {
+		given: 'A JSON codec'
+		JsonCodec<JsonNode, Object> codec = new JsonCodec<JsonNode, Object>(JsonNode);
+
+		when: 'The decoder is passed some JSON'
+		JsonNode decoded
+		Function<Buffer, JsonNode> decoder = codec.decoder({ decoded = it} as Consumer<JsonNode>)
+		decoder.apply(Buffer.wrap("{\"a\": \"alpha\"}"));
+
+		then: 'The decoded JsonNode is an object node with the expected entries'
+		decoded instanceof ObjectNode
+		decoded.get('a').textValue() == 'alpha'
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/encoding/kryo/KryoCodecSpec.groovy b/reactor-core/src/test/groovy/reactor/io/encoding/kryo/KryoCodecSpec.groovy
new file mode 100644
index 0000000..0f9c368
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/encoding/kryo/KryoCodecSpec.groovy
@@ -0,0 +1,57 @@
+package reactor.io.encoding.kryo
+
+import com.esotericsoftware.kryo.Kryo
+import reactor.io.Buffer
+import spock.lang.Specification
+
+/**
+ * @author Jon Brisbin
+ */
+class KryoCodecSpec extends Specification {
+
+	Kryo kryo
+
+	def setup() {
+		kryo = new Kryo()
+		kryo.register(RichObject)
+	}
+
+	def "properly serializes and deserializes objects"() {
+
+		given: "a Kryo codec and a Buffer"
+			def codec = new KryoCodec<RichObject, RichObject>(kryo, true)
+			RichObject obj = new RichObject("first", 0.5f, 100l)
+			Buffer buffer
+
+		when: "an objects are serialized"
+			buffer = codec.encoder().apply(obj)
+
+		then: "all objects were serialized"
+			buffer.remaining() == 78
+
+		when: "an object is deserialized"
+			RichObject newObj = codec.decoder(null).apply(buffer)
+
+		then: "the object was deserialized"
+			newObj.name == "first"
+			newObj.percent == 0.5f
+			newObj.total == 100l
+
+	}
+
+	static class RichObject {
+		String name
+		Float percent
+		Long total
+
+		RichObject() {
+		}
+
+		RichObject(String name, Float percent, Long total) {
+			this.name = name
+			this.percent = percent
+			this.total = total
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/ProtobufCodecSpec.groovy b/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/ProtobufCodecSpec.groovy
new file mode 100644
index 0000000..d2eac51
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/ProtobufCodecSpec.groovy
@@ -0,0 +1,43 @@
+package reactor.io.encoding.protobuf
+
+import reactor.io.Buffer
+import spock.lang.Specification
+
+/**
+ * @author Jon Brisbin
+ */
+class ProtobufCodecSpec extends Specification {
+
+	TestObjects.RichObject obj
+
+	def setup() {
+		obj = TestObjects.RichObject.newBuilder().
+				setName("first").
+				setPercent(0.5f).
+				setTotal(100l).
+				build()
+	}
+
+	def "properly serializes and deserializes objects"() {
+
+		given: "a ProtobufCodec and a Buffer"
+			def codec = new ProtobufCodec<TestObjects.RichObject, TestObjects.RichObject>()
+			Buffer buffer
+
+		when: "an object is serialized"
+			buffer = codec.encoder().apply(obj)
+
+		then: "the object ws serialized"
+			buffer.remaining() == 73
+
+		when: "an object is deserialized"
+			TestObjects.RichObject newObj = codec.decoder(null).apply(buffer)
+
+		then: "the object was deserialized"
+			newObj.name == "first"
+			newObj.percent == 0.5f
+			newObj.total == 100l
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/TestObjects.java b/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/TestObjects.java
new file mode 100644
index 0000000..93c4efe
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/TestObjects.java
@@ -0,0 +1,707 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: test_objects.proto
+
+package reactor.io.encoding.protobuf;
+
+public final class TestObjects {
+  private TestObjects() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+  }
+  public interface RichObjectOrBuilder
+      extends com.google.protobuf.MessageOrBuilder {
+
+    // required string name = 1;
+    /**
+     * <code>required string name = 1;</code>
+     */
+    boolean hasName();
+    /**
+     * <code>required string name = 1;</code>
+     */
+    java.lang.String getName();
+    /**
+     * <code>required string name = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getNameBytes();
+
+    // required float percent = 2;
+    /**
+     * <code>required float percent = 2;</code>
+     */
+    boolean hasPercent();
+    /**
+     * <code>required float percent = 2;</code>
+     */
+    float getPercent();
+
+    // required int64 total = 3;
+    /**
+     * <code>required int64 total = 3;</code>
+     */
+    boolean hasTotal();
+    /**
+     * <code>required int64 total = 3;</code>
+     */
+    long getTotal();
+  }
+  /**
+   * Protobuf type {@code reactor.io.encoding.protobuf.RichObject}
+   */
+  public static final class RichObject extends
+      com.google.protobuf.GeneratedMessage
+      implements RichObjectOrBuilder {
+    // Use RichObject.newBuilder() to construct.
+    private RichObject(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
+      super(builder);
+      this.unknownFields = builder.getUnknownFields();
+    }
+    private RichObject(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
+
+    private static final RichObject defaultInstance;
+    public static RichObject getDefaultInstance() {
+      return defaultInstance;
+    }
+
+    public RichObject getDefaultInstanceForType() {
+      return defaultInstance;
+    }
+
+    private final com.google.protobuf.UnknownFieldSet unknownFields;
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+        getUnknownFields() {
+      return this.unknownFields;
+    }
+    private RichObject(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      initFields();
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownField(input, unknownFields,
+                                     extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              bitField0_ |= 0x00000001;
+              name_ = input.readBytes();
+              break;
+            }
+            case 21: {
+              bitField0_ |= 0x00000002;
+              percent_ = input.readFloat();
+              break;
+            }
+            case 24: {
+              bitField0_ |= 0x00000004;
+              total_ = input.readInt64();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e.getMessage()).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return reactor.io.encoding.protobuf.TestObjects.internal_static_reactor_io_encoding_protobuf_RichObject_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return reactor.io.encoding.protobuf.TestObjects.internal_static_reactor_io_encoding_protobuf_RichObject_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              reactor.io.encoding.protobuf.TestObjects.RichObject.class, reactor.io.encoding.protobuf.TestObjects.RichObject.Builder.class);
+    }
+
+    public static com.google.protobuf.Parser<RichObject> PARSER =
+        new com.google.protobuf.AbstractParser<RichObject>() {
+      public RichObject parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new RichObject(input, extensionRegistry);
+      }
+    };
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<RichObject> getParserForType() {
+      return PARSER;
+    }
+
+    private int bitField0_;
+    // required string name = 1;
+    public static final int NAME_FIELD_NUMBER = 1;
+    private java.lang.Object name_;
+    /**
+     * <code>required string name = 1;</code>
+     */
+    public boolean hasName() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>required string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (bs.isValidUtf8()) {
+          name_ = s;
+        }
+        return s;
+      }
+    }
+    /**
+     * <code>required string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    // required float percent = 2;
+    public static final int PERCENT_FIELD_NUMBER = 2;
+    private float percent_;
+    /**
+     * <code>required float percent = 2;</code>
+     */
+    public boolean hasPercent() {
+      return ((bitField0_ & 0x00000002) == 0x00000002);
+    }
+    /**
+     * <code>required float percent = 2;</code>
+     */
+    public float getPercent() {
+      return percent_;
+    }
+
+    // required int64 total = 3;
+    public static final int TOTAL_FIELD_NUMBER = 3;
+    private long total_;
+    /**
+     * <code>required int64 total = 3;</code>
+     */
+    public boolean hasTotal() {
+      return ((bitField0_ & 0x00000004) == 0x00000004);
+    }
+    /**
+     * <code>required int64 total = 3;</code>
+     */
+    public long getTotal() {
+      return total_;
+    }
+
+    private void initFields() {
+      name_ = "";
+      percent_ = 0F;
+      total_ = 0L;
+    }
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized != -1) return isInitialized == 1;
+
+      if (!hasName()) {
+        memoizedIsInitialized = 0;
+        return false;
+      }
+      if (!hasPercent()) {
+        memoizedIsInitialized = 0;
+        return false;
+      }
+      if (!hasTotal()) {
+        memoizedIsInitialized = 0;
+        return false;
+      }
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      if (((bitField0_ & 0x00000001) == 0x00000001)) {
+        output.writeBytes(1, getNameBytes());
+      }
+      if (((bitField0_ & 0x00000002) == 0x00000002)) {
+        output.writeFloat(2, percent_);
+      }
+      if (((bitField0_ & 0x00000004) == 0x00000004)) {
+        output.writeInt64(3, total_);
+      }
+      getUnknownFields().writeTo(output);
+    }
+
+    private int memoizedSerializedSize = -1;
+    public int getSerializedSize() {
+      int size = memoizedSerializedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (((bitField0_ & 0x00000001) == 0x00000001)) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(1, getNameBytes());
+      }
+      if (((bitField0_ & 0x00000002) == 0x00000002)) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeFloatSize(2, percent_);
+      }
+      if (((bitField0_ & 0x00000004) == 0x00000004)) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt64Size(3, total_);
+      }
+      size += getUnknownFields().getSerializedSize();
+      memoizedSerializedSize = size;
+      return size;
+    }
+
+    private static final long serialVersionUID = 0L;
+    @java.lang.Override
+    protected java.lang.Object writeReplace()
+        throws java.io.ObjectStreamException {
+      return super.writeReplace();
+    }
+
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return PARSER.parseFrom(input);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return PARSER.parseFrom(input, extensionRegistry);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return PARSER.parseDelimitedFrom(input);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return PARSER.parseDelimitedFrom(input, extensionRegistry);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return PARSER.parseFrom(input);
+    }
+    public static reactor.io.encoding.protobuf.TestObjects.RichObject parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return PARSER.parseFrom(input, extensionRegistry);
+    }
+
+    public static Builder newBuilder() { return Builder.create(); }
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder(reactor.io.encoding.protobuf.TestObjects.RichObject prototype) {
+      return newBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() { return newBuilder(this); }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code reactor.io.encoding.protobuf.RichObject}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessage.Builder<Builder>
+       implements reactor.io.encoding.protobuf.TestObjects.RichObjectOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return reactor.io.encoding.protobuf.TestObjects.internal_static_reactor_io_encoding_protobuf_RichObject_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return reactor.io.encoding.protobuf.TestObjects.internal_static_reactor_io_encoding_protobuf_RichObject_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                reactor.io.encoding.protobuf.TestObjects.RichObject.class, reactor.io.encoding.protobuf.TestObjects.RichObject.Builder.class);
+      }
+
+      // Construct using reactor.io.encoding.protobuf.TestObjects.RichObject.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+        }
+      }
+      private static Builder create() {
+        return new Builder();
+      }
+
+      public Builder clear() {
+        super.clear();
+        name_ = "";
+        bitField0_ = (bitField0_ & ~0x00000001);
+        percent_ = 0F;
+        bitField0_ = (bitField0_ & ~0x00000002);
+        total_ = 0L;
+        bitField0_ = (bitField0_ & ~0x00000004);
+        return this;
+      }
+
+      public Builder clone() {
+        return create().mergeFrom(buildPartial());
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return reactor.io.encoding.protobuf.TestObjects.internal_static_reactor_io_encoding_protobuf_RichObject_descriptor;
+      }
+
+      public reactor.io.encoding.protobuf.TestObjects.RichObject getDefaultInstanceForType() {
+        return reactor.io.encoding.protobuf.TestObjects.RichObject.getDefaultInstance();
+      }
+
+      public reactor.io.encoding.protobuf.TestObjects.RichObject build() {
+        reactor.io.encoding.protobuf.TestObjects.RichObject result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public reactor.io.encoding.protobuf.TestObjects.RichObject buildPartial() {
+        reactor.io.encoding.protobuf.TestObjects.RichObject result = new reactor.io.encoding.protobuf.TestObjects.RichObject(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+          to_bitField0_ |= 0x00000001;
+        }
+        result.name_ = name_;
+        if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+          to_bitField0_ |= 0x00000002;
+        }
+        result.percent_ = percent_;
+        if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
+          to_bitField0_ |= 0x00000004;
+        }
+        result.total_ = total_;
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof reactor.io.encoding.protobuf.TestObjects.RichObject) {
+          return mergeFrom((reactor.io.encoding.protobuf.TestObjects.RichObject)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(reactor.io.encoding.protobuf.TestObjects.RichObject other) {
+        if (other == reactor.io.encoding.protobuf.TestObjects.RichObject.getDefaultInstance()) return this;
+        if (other.hasName()) {
+          bitField0_ |= 0x00000001;
+          name_ = other.name_;
+          onChanged();
+        }
+        if (other.hasPercent()) {
+          setPercent(other.getPercent());
+        }
+        if (other.hasTotal()) {
+          setTotal(other.getTotal());
+        }
+        this.mergeUnknownFields(other.getUnknownFields());
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        if (!hasName()) {
+          
+          return false;
+        }
+        if (!hasPercent()) {
+          
+          return false;
+        }
+        if (!hasTotal()) {
+          
+          return false;
+        }
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        reactor.io.encoding.protobuf.TestObjects.RichObject parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (reactor.io.encoding.protobuf.TestObjects.RichObject) e.getUnfinishedMessage();
+          throw e;
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      // required string name = 1;
+      private java.lang.Object name_ = "";
+      /**
+       * <code>required string name = 1;</code>
+       */
+      public boolean hasName() {
+        return ((bitField0_ & 0x00000001) == 0x00000001);
+      }
+      /**
+       * <code>required string name = 1;</code>
+       */
+      public java.lang.String getName() {
+        java.lang.Object ref = name_;
+        if (!(ref instanceof java.lang.String)) {
+          java.lang.String s = ((com.google.protobuf.ByteString) ref)
+              .toStringUtf8();
+          name_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>required string name = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getNameBytes() {
+        java.lang.Object ref = name_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          name_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>required string name = 1;</code>
+       */
+      public Builder setName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+        name_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>required string name = 1;</code>
+       */
+      public Builder clearName() {
+        bitField0_ = (bitField0_ & ~0x00000001);
+        name_ = getDefaultInstance().getName();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>required string name = 1;</code>
+       */
+      public Builder setNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+        name_ = value;
+        onChanged();
+        return this;
+      }
+
+      // required float percent = 2;
+      private float percent_ ;
+      /**
+       * <code>required float percent = 2;</code>
+       */
+      public boolean hasPercent() {
+        return ((bitField0_ & 0x00000002) == 0x00000002);
+      }
+      /**
+       * <code>required float percent = 2;</code>
+       */
+      public float getPercent() {
+        return percent_;
+      }
+      /**
+       * <code>required float percent = 2;</code>
+       */
+      public Builder setPercent(float value) {
+        bitField0_ |= 0x00000002;
+        percent_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>required float percent = 2;</code>
+       */
+      public Builder clearPercent() {
+        bitField0_ = (bitField0_ & ~0x00000002);
+        percent_ = 0F;
+        onChanged();
+        return this;
+      }
+
+      // required int64 total = 3;
+      private long total_ ;
+      /**
+       * <code>required int64 total = 3;</code>
+       */
+      public boolean hasTotal() {
+        return ((bitField0_ & 0x00000004) == 0x00000004);
+      }
+      /**
+       * <code>required int64 total = 3;</code>
+       */
+      public long getTotal() {
+        return total_;
+      }
+      /**
+       * <code>required int64 total = 3;</code>
+       */
+      public Builder setTotal(long value) {
+        bitField0_ |= 0x00000004;
+        total_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>required int64 total = 3;</code>
+       */
+      public Builder clearTotal() {
+        bitField0_ = (bitField0_ & ~0x00000004);
+        total_ = 0L;
+        onChanged();
+        return this;
+      }
+
+      // @@protoc_insertion_point(builder_scope:reactor.io.encoding.protobuf.RichObject)
+    }
+
+    static {
+      defaultInstance = new RichObject(true);
+      defaultInstance.initFields();
+    }
+
+    // @@protoc_insertion_point(class_scope:reactor.io.encoding.protobuf.RichObject)
+  }
+
+  private static com.google.protobuf.Descriptors.Descriptor
+    internal_static_reactor_io_encoding_protobuf_RichObject_descriptor;
+  private static
+    com.google.protobuf.GeneratedMessage.FieldAccessorTable
+      internal_static_reactor_io_encoding_protobuf_RichObject_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\022test_objects.proto\022\034reactor.io.encodin" +
+      "g.protobuf\":\n\nRichObject\022\014\n\004name\030\001 \002(\t\022\017" +
+      "\n\007percent\030\002 \002(\002\022\r\n\005total\030\003 \002(\003"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+      new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
+        public com.google.protobuf.ExtensionRegistry assignDescriptors(
+            com.google.protobuf.Descriptors.FileDescriptor root) {
+          descriptor = root;
+          internal_static_reactor_io_encoding_protobuf_RichObject_descriptor =
+            getDescriptor().getMessageTypes().get(0);
+          internal_static_reactor_io_encoding_protobuf_RichObject_fieldAccessorTable = new
+            com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+              internal_static_reactor_io_encoding_protobuf_RichObject_descriptor,
+              new java.lang.String[] { "Name", "Percent", "Total", });
+          return null;
+        }
+      };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/test_objects.proto b/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/test_objects.proto
new file mode 100644
index 0000000..f88e132
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/io/encoding/protobuf/test_objects.proto
@@ -0,0 +1,7 @@
+package reactor.io.encoding.protobuf;
+
+message RichObject {
+	required string name = 1;
+	required float percent = 2;
+	required int64 total = 3;
+}
\ No newline at end of file
diff --git a/reactor-core/src/test/groovy/reactor/queue/PersistentQueueSpec.groovy b/reactor-core/src/test/groovy/reactor/queue/PersistentQueueSpec.groovy
new file mode 100644
index 0000000..f15b9c2
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/queue/PersistentQueueSpec.groovy
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.queue
+
+import net.openhft.chronicle.ChronicleConfig
+import net.openhft.chronicle.tools.ChronicleTools
+import reactor.io.encoding.StandardCodecs
+import reactor.io.encoding.json.JsonCodec
+import spock.lang.Specification
+
+/**
+ * @author Jon Brisbin
+ */
+class PersistentQueueSpec extends Specification {
+
+	static QueuePersistor<String> persistor() {
+		def config = ChronicleConfig.TEST.clone()
+
+		new IndexedChronicleQueuePersistor<String>(
+				"persistent-queue",
+				StandardCodecs.STRING_CODEC,
+				false,
+				false,
+				config
+		)
+	}
+
+	static <T> QueuePersistor<T> jsonPersistor(Class<T> type) {
+		def config = ChronicleConfig.TEST.clone()
+
+		new IndexedChronicleQueuePersistor<T>(
+				"persistent-queue",
+				new JsonCodec<T, T>(type),
+				false,
+				false,
+				config
+		)
+	}
+
+	def cleanup() {
+		ChronicleTools.deleteOnExit("persistent-queue")
+	}
+
+	def "File-based PersistentQueue is sharable"() {
+
+		given:
+			def wq = new PersistentQueue<String>(persistor())
+			def strings1 = []
+			def rq = new PersistentQueue<String>(persistor())
+			def strings2 = []
+
+		when:
+			"data is written to a write Queue"
+			(1..100).each {
+				def s = "test $it".toString()
+				wq.offer(s)
+				strings1 << s
+			}
+
+		then:
+			"all data was written"
+			wq.size() == 100
+
+		when:
+			"data is read from a shared read Queue"
+			rq.each {
+				strings2 << it
+			}
+
+		then:
+			"all data was read"
+			strings1 == strings2
+
+	}
+
+	def "Java Chronicle-based PersistentQueue is performant"() {
+
+		given:
+			def wq = new PersistentQueue(jsonPersistor(Map))
+			def msgs = 10000
+
+		when:
+			"data is written to the Queue"
+			def start = System.currentTimeMillis()
+			def count = 0
+			for (int i in 1..msgs) {
+				wq.offer(["test": i])
+			}
+			for (def m in wq) {
+				count++
+			}
+			def end = System.currentTimeMillis()
+			double elapsed = end - start
+			int throughput = msgs / (elapsed / 1000)
+			println "throughput: ${throughput}/sec in ${(int) elapsed}ms"
+
+		then:
+			"throughput is sufficient"
+			count == msgs
+			throughput > 1000
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/queue/QueuePersistorSpec.groovy b/reactor-core/src/test/groovy/reactor/queue/QueuePersistorSpec.groovy
new file mode 100644
index 0000000..c4c8874
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/queue/QueuePersistorSpec.groovy
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.queue
+
+import net.openhft.chronicle.ChronicleConfig
+import reactor.io.encoding.StandardCodecs
+import spock.lang.Specification
+
+/**
+ * @author Jon Brisbin
+ */
+class QueuePersistorSpec extends Specification {
+
+	def "InMemoryQueuePersistor persists objects"() {
+
+		given:
+			"an InMemoryQueuePersistor"
+			def persistor = new InMemoryQueuePersistor()
+			def obj = new Object()
+
+		when:
+			"an Object is persisted"
+			def id = persistor.offer().apply(obj)
+
+		then:
+			"the Object was persisted"
+			id > -1
+			persistor.get().apply(id) == obj
+
+		when:
+			"an Object is removed"
+			persistor.remove().get()
+
+		then:
+			"the Object was removed"
+			null == persistor.get().apply(id)
+			persistor.size() == 0
+
+		cleanup:
+			persistor.close()
+
+	}
+
+	def "IndexedChronicleQueuePersistor persists objects"() {
+
+		given:
+			"an IndexedChronicleQueuePersistor"
+			def persistor = new IndexedChronicleQueuePersistor<String>(
+					"queue-persistor",
+					StandardCodecs.STRING_CODEC,
+					true,
+					true,
+					ChronicleConfig.TEST.clone()
+			)
+			def obj = "Hello World!"
+
+		when:
+			"an object is persisted"
+			def id = persistor.offer().apply(obj)
+
+		then:
+			"the object was persisted"
+			id > -1
+			persistor.get().apply(id) == obj
+			persistor.hasNext()
+
+		when:
+			"the object is removed"
+			persistor.remove().get()
+
+		then:
+			"the object was removed"
+			persistor.size() == 0
+
+		cleanup:
+			persistor.close()
+
+	}
+
+}
diff --git a/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerBusySpinStrategy.groovy b/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerBusySpinStrategy.groovy
new file mode 100644
index 0000000..7a05403
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerBusySpinStrategy.groovy
@@ -0,0 +1,70 @@
+package reactor.timer
+
+import reactor.function.Consumer
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Oleksandr Petrov
+ */
+class HashWheelTimerBusySpinStrategy extends Specification {
+
+  def period = 50
+
+  def "HashWheelTimer can schedule recurring tasks"() {
+
+    given:
+    "a new timer"
+    def timer = new HashWheelTimer(10, 8, new HashWheelTimer.BusySpinWait())
+    def latch = new CountDownLatch(10)
+
+    when:
+    "a task is submitted"
+    timer.schedule(
+            { Long now -> latch.countDown() } as Consumer<Long>,
+            period,
+            TimeUnit.MILLISECONDS,
+            period
+    )
+
+    then:
+    "the latch was counted down"
+    latch.await(1, TimeUnit.SECONDS)
+    timer.cancel()
+
+  }
+
+  def "HashWheelTimer can delay submitted tasks"() {
+
+    given:
+    "a new timer"
+    def delay = 500
+    def timer = new HashWheelTimer(10, 512, new HashWheelTimer.BusySpinWait())
+    def latch = new CountDownLatch(1)
+    def start = System.currentTimeMillis()
+    def elapsed = 0
+    //def actualTimeWithinBounds = true
+
+    when:
+    "a task is submitted"
+    timer.submit(
+            { Long now ->
+              elapsed = System.currentTimeMillis() - start
+              latch.countDown()
+            } as Consumer<Long>,
+            delay,
+            TimeUnit.MILLISECONDS
+    )
+
+    then:
+    "the latch was counted down"
+    latch.await(1, TimeUnit.SECONDS)
+    elapsed >= delay
+    elapsed < delay * 2
+    timer.cancel()
+  }
+
+}
+
diff --git a/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerSleepWaitStrategy.groovy b/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerSleepWaitStrategy.groovy
new file mode 100644
index 0000000..e69281c
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerSleepWaitStrategy.groovy
@@ -0,0 +1,69 @@
+package reactor.timer
+
+import reactor.function.Consumer
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Oleksandr Petrov
+ */
+class HashWheelTimerSleepWaitStrategy extends Specification {
+
+  def period = 50
+
+  def "HashWheelTimer can schedule recurring tasks"() {
+
+    given:
+    "a new timer"
+    def timer = new HashWheelTimer(10, 8, new HashWheelTimer.SleepWait())
+    def latch = new CountDownLatch(10)
+
+    when:
+    "a task is submitted"
+    timer.schedule(
+            { Long now -> latch.countDown() } as Consumer<Long>,
+            period,
+            TimeUnit.MILLISECONDS,
+            period
+    )
+
+    then:
+    "the latch was counted down"
+    latch.await(1, TimeUnit.SECONDS)
+
+  }
+
+  def "HashWheelTimer can delay submitted tasks"() {
+
+    given:
+    "a new timer"
+    def delay = 500
+    def timer = new HashWheelTimer(10, 8, new HashWheelTimer.SleepWait())
+    def latch = new CountDownLatch(1)
+    def start = System.currentTimeMillis()
+    def elapsed = 0
+    //def actualTimeWithinBounds = true
+
+    when:
+    "a task is submitted"
+    timer.submit(
+            { Long now ->
+              elapsed = System.currentTimeMillis() - start
+              latch.countDown()
+            } as Consumer<Long>,
+            delay,
+            TimeUnit.MILLISECONDS
+    )
+
+    then:
+    "the latch was counted down"
+    latch.await(1, TimeUnit.SECONDS)
+    elapsed >= delay
+    elapsed < delay * 2
+
+  }
+
+}
+
diff --git a/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerYieldingStrategy.groovy b/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerYieldingStrategy.groovy
new file mode 100644
index 0000000..99a66fc
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/timer/HashWheelTimerYieldingStrategy.groovy
@@ -0,0 +1,72 @@
+package reactor.timer;
+
+import reactor.function.Consumer
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Oleksandr Petrov
+ */
+class HashWheelTimerYieldingStrategy extends Specification {
+
+  def period = 50
+
+  def "HashWheelTimer can schedule recurring tasks"() {
+
+    given:
+    "a new timer"
+    def timer = new HashWheelTimer(10, 8, new HashWheelTimer.YieldingWait())
+    def latch = new CountDownLatch(10)
+
+    when:
+    "a task is submitted"
+    timer.schedule(
+            { Long now -> latch.countDown() } as Consumer<Long>,
+            period,
+            TimeUnit.MILLISECONDS,
+            period
+    )
+
+    then:
+    "the latch was counted down"
+    latch.await(1, TimeUnit.SECONDS)
+    timer.cancel()
+
+  }
+
+  def "HashWheelTimer can delay submitted tasks"() {
+
+    given:
+    "a new timer"
+    def delay = 500
+    def timer = new HashWheelTimer(10, 512, new HashWheelTimer.YieldingWait())
+    def latch = new CountDownLatch(1)
+    def start = System.currentTimeMillis()
+    def elapsed = 0
+    //def actualTimeWithinBounds = true
+
+    when:
+    "a task is submitted"
+    timer.submit(
+            { Long now ->
+              elapsed = System.currentTimeMillis() - start
+              latch.countDown()
+            } as Consumer<Long>,
+            delay,
+            TimeUnit.MILLISECONDS
+    )
+
+    then:
+    "the latch was counted down"
+    latch.await(1, TimeUnit.SECONDS)
+    elapsed >= delay
+    elapsed < delay * 2
+    timer.cancel()
+  }
+
+}
+
+
+
diff --git a/reactor-core/src/test/groovy/reactor/timer/SimpleHashWheelTimerSpec.groovy b/reactor-core/src/test/groovy/reactor/timer/SimpleHashWheelTimerSpec.groovy
new file mode 100644
index 0000000..860d9c2
--- /dev/null
+++ b/reactor-core/src/test/groovy/reactor/timer/SimpleHashWheelTimerSpec.groovy
@@ -0,0 +1,70 @@
+package reactor.timer
+
+import reactor.function.Consumer
+import spock.lang.Ignore
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Jon Brisbin
+ */
+class SimpleHashWheelTimerSpec extends Specification {
+
+	def period = 50
+
+  def "HashWheelTimer can schedule recurring tasks"() {
+
+	  given:
+      "a new timer"
+      def timer = new SimpleHashWheelTimer()
+      def latch = new CountDownLatch(10)
+
+    when:
+      "a task is submitted"
+      timer.schedule(
+          { Long now -> latch.countDown() } as Consumer<Long>,
+          period,
+          TimeUnit.MILLISECONDS,
+          period
+      )
+
+    then:
+      "the latch was counted down"
+      latch.await(1, TimeUnit.SECONDS)
+
+  }
+
+	@Ignore
+  def "HashWheelTimer can delay submitted tasks"() {
+
+    given:
+      "a new timer"
+      def delay = 500
+      def timer = new SimpleHashWheelTimer()
+      def latch = new CountDownLatch(1)
+      def start = System.currentTimeMillis()
+      def elapsed = 0
+      //def actualTimeWithinBounds = true
+
+    when:
+      "a task is submitted"
+      timer.submit(
+          { Long now ->
+            elapsed = System.currentTimeMillis() - start
+	          latch.countDown()
+          } as Consumer<Long>,
+          delay,
+          TimeUnit.MILLISECONDS
+      )
+
+    then:
+      "the latch was counted down"
+      latch.await(1, TimeUnit.SECONDS)
+	    elapsed >= delay
+	    elapsed < delay * 2
+
+  }
+
+}
diff --git a/reactor-core/src/test/java/reactor/AbstractPerformanceTest.java b/reactor-core/src/test/java/reactor/AbstractPerformanceTest.java
new file mode 100644
index 0000000..9a605aa
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/AbstractPerformanceTest.java
@@ -0,0 +1,84 @@
+package reactor;
+
+import jsr166e.LongAdder;
+import org.junit.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class AbstractPerformanceTest {
+
+	protected static final Timer TIMER = new Timer();
+	protected static volatile long now;
+
+	static {
+		TIMER.schedule(
+				new TimerTask() {
+					@Override
+					public void run() {
+						now = System.currentTimeMillis();
+					}
+				},
+				0,
+				200
+		);
+	}
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+
+	protected ExecutorService pool;
+	protected Environment     env;
+	protected LongAdder       counter;
+	protected long            start;
+
+	@Before
+	public void setup() {
+		pool = Executors.newCachedThreadPool();
+		counter = new LongAdder();
+		env = new Environment();
+	}
+
+	protected long getTimeout() {
+		return 1000;
+	}
+
+	protected void startThroughputTest(String type) {
+		log.info("Starting {} throughput test...", type);
+		start = System.currentTimeMillis();
+	}
+
+	protected void stopThroughputTest(String type) {
+		long end = System.currentTimeMillis();
+		double elapsed = end - start;
+		long throughput = (long) (counter.longValue() / (elapsed / 1000));
+
+		log.info("{} throughput: {}/s in {}ms", type, throughput, (int) elapsed);
+	}
+
+	protected boolean withinTimeout() {
+		return now - start < getTimeout();
+	}
+
+	protected void fork(int threadCount, Runnable r) throws ExecutionException, InterruptedException {
+		Future[] futures = new Future[threadCount];
+		for (int i = 0; i < threadCount; i++) {
+			futures[i] = pool.submit(r);
+		}
+		for (Future f : futures) {
+			if (null != f) {
+				f.get();
+			}
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/AbstractReactorTest.java b/reactor-core/src/test/java/reactor/AbstractReactorTest.java
new file mode 100644
index 0000000..87a8522
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/AbstractReactorTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor;
+
+import org.junit.Before;
+import reactor.core.Environment;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class AbstractReactorTest {
+
+	protected Environment env;
+
+	@Before
+	public void loadEnv() {
+		env = new Environment();
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/alloc/AllocationTests.java b/reactor-core/src/test/java/reactor/alloc/AllocationTests.java
new file mode 100644
index 0000000..1c22d92
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/alloc/AllocationTests.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.alloc;
+
+import org.junit.Test;
+import reactor.AbstractPerformanceTest;
+import reactor.core.Environment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Jon Brisbin
+ */
+public class AllocationTests extends AbstractPerformanceTest {
+
+	@Override
+	public void setup() {
+		super.setup();
+		pool = Executors.newFixedThreadPool(Environment.PROCESSORS);
+	}
+
+	@Test
+	public void threadPartitionedAllocatorAllocatesByThread() throws Exception {
+		int threadCnt = Environment.PROCESSORS;
+		CountDownLatch latch = new CountDownLatch(threadCnt);
+		Allocator<RecyclableNumber> alloc = new ThreadPartitionedAllocator<>(RecyclableNumber::new);
+		List<Long> threadIds = Collections.synchronizedList(new ArrayList<Long>());
+
+		fork(threadCnt, () -> {
+			long threadId = Thread.currentThread().getId();
+			alloc.allocate().get().setValue(threadId);
+			threadIds.add(threadId);
+		});
+
+		Thread.sleep(500);
+
+		fork(threadCnt, () -> {
+			if (threadIds.contains(alloc.allocate().get().longValue())) {
+				latch.countDown();
+			}
+		});
+
+		assertTrue("latch was counted down", latch.await(5, TimeUnit.SECONDS));
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/alloc/EventAllocatorTests.java b/reactor-core/src/test/java/reactor/alloc/EventAllocatorTests.java
new file mode 100644
index 0000000..2d4e231
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/alloc/EventAllocatorTests.java
@@ -0,0 +1,24 @@
+package reactor.alloc;
+
+import org.junit.Test;
+import reactor.event.Event;
+import reactor.event.alloc.EventAllocator;
+
+import static junit.framework.Assert.assertTrue;
+
+public class EventAllocatorTests {
+
+  @Test
+  public void eventAllocatorTest() {
+    EventAllocator eventAllocator = EventAllocator.defaultEventAllocator();
+
+    Event<String> eStr = eventAllocator.get(String.class).get();
+    eStr.setData("string");
+    assertTrue("String data is settable into the String event", eStr.getData() == "string");
+
+
+    Event<Integer> eInt = eventAllocator.get(Integer.class).get();
+    eInt.setData(1);
+    assertTrue("Integer data is settable into the Integer event", eInt.getData() == 1);
+  }
+}
diff --git a/reactor-core/src/test/java/reactor/convert/ConstructorParameterConverterTests.java b/reactor-core/src/test/java/reactor/convert/ConstructorParameterConverterTests.java
new file mode 100644
index 0000000..c811f31
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/convert/ConstructorParameterConverterTests.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.convert;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import reactor.convert.StandardConverters.ConstructorParameterConverter;
+
+/**
+ * @author Andy Wilkinson
+ */
+public class ConstructorParameterConverterTests {
+
+	private final Converter converter = ConstructorParameterConverter.INSTANCE;
+
+	@Test
+	public void canConvertWhenTargetHasSingleArgConstructorThatTakesTheSourceType() {
+		assertTrue(converter.canConvert(Integer.class, Target.class));
+	}
+
+	@Test
+	public void canConvertWhenTargetHasASingleArgConstructorThatTakesATypeThatTheSourceCanBeConvertedTo() {
+		assertTrue(converter.canConvert(String.class, Target.class));
+	}
+
+	@Test
+	public void conversionCreatesNewInstanceOfTargetUsingSource() {
+		Target target = converter.convert(Integer.valueOf(47), Target.class);
+		assertEquals(Integer.valueOf(47), target.value);
+	}
+
+	@Test
+	public void conversionCreatesNewInstanceOfTargetUsingConvertedSource() {
+		Target target = converter.convert("47", Target.class);
+		assertEquals(Integer.valueOf(47), target.value);
+	}
+
+	public static final class Target {
+
+		final Integer value;
+
+		public Target(Integer value) {
+			this.value = value;
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/core/AwaitTests.java b/reactor-core/src/test/java/reactor/core/AwaitTests.java
new file mode 100644
index 0000000..48c116f
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/core/AwaitTests.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core;
+
+import org.junit.Test;
+import reactor.AbstractReactorTest;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.spec.Promises;
+import reactor.core.spec.Reactors;
+import reactor.event.dispatch.ThreadPoolExecutorDispatcher;
+import reactor.function.Consumer;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * @author Jon Brisbin
+ */
+ at SuppressWarnings({"unchecked", "rawtypes"})
+public class AwaitTests extends AbstractReactorTest {
+
+	@Test
+	public void testAwaitDoesntBlockUnnecessarily() throws InterruptedException {
+		ThreadPoolExecutorDispatcher dispatcher = new ThreadPoolExecutorDispatcher(4, 64);
+
+		Reactor innerReactor = Reactors.reactor().env(env).dispatcher(dispatcher).get();
+
+		for(int i = 0; i < 1000; i++) {
+			final Deferred<String, Promise<String>> deferred = Promises.<String>defer()
+			                                                           .env(env)
+			                                                           .dispatcher("threadPoolExecutor")
+			                                                           .get();
+			final CountDownLatch latch = new CountDownLatch(1);
+
+			Promise<String> promise = deferred.compose();
+			promise.onSuccess(new Consumer<String>() {
+
+				@Override
+				public void accept(String t) {
+					latch.countDown();
+				}
+			});
+			innerReactor.schedule(new Consumer() {
+
+				@Override
+				public void accept(Object t) {
+					deferred.accept("foo");
+				}
+
+			}, null);
+
+			assertThat("latch is counted down", latch.await(5, TimeUnit.SECONDS));
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/core/EnvironmentTest.java b/reactor-core/src/test/java/reactor/core/EnvironmentTest.java
new file mode 100644
index 0000000..ab6f433
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/core/EnvironmentTest.java
@@ -0,0 +1,21 @@
+package reactor.core;
+
+import org.junit.Test;
+import reactor.AbstractReactorTest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class EnvironmentTest extends AbstractReactorTest {
+
+    @Test
+    public void testGetDispatcherThrowsExceptionWhenNoDispatcherIsFound() throws Exception {
+        try {
+            env.getDispatcher("NonexistingDispatcher");
+            fail("Should have thrown an exception");
+        } catch ( IllegalArgumentException e ) {
+            assertEquals("No Dispatcher found for name 'NonexistingDispatcher'", e.getMessage());
+        }
+    }
+
+}
diff --git a/reactor-core/src/test/java/reactor/core/composable/ComposableTests.java b/reactor-core/src/test/java/reactor/core/composable/ComposableTests.java
new file mode 100644
index 0000000..d4a4ecf
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/core/composable/ComposableTests.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.composable;
+
+import com.gs.collections.impl.map.mutable.ConcurrentHashMap;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+import reactor.AbstractReactorTest;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.spec.Promises;
+import reactor.core.composable.spec.Streams;
+import reactor.core.spec.Reactors;
+import reactor.event.Event;
+import reactor.event.selector.Selector;
+import reactor.event.selector.Selectors;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.function.Predicate;
+import reactor.function.support.Tap;
+import reactor.tuple.Tuple2;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.number.OrderingComparison.lessThan;
+import static org.junit.Assert.*;
+
+/**
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class ComposableTests extends AbstractReactorTest {
+
+	static final String2Integer STRING_2_INTEGER = new String2Integer();
+
+	@Test
+	public void testComposeFromSingleValue() throws InterruptedException {
+		Stream<String> stream = Streams.defer("Hello World!").get();
+		Stream<String> s =
+				stream
+						.map(new Function<String, String>() {
+							@Override
+							public String apply(String s) {
+								return "Goodbye then!";
+							}
+						});
+
+		await(s, is("Goodbye then!"));
+	}
+
+	@Test
+	public void testComposeFromMultipleValues() throws InterruptedException {
+		Stream<String> stream = Streams.defer(Arrays.asList("1", "2", "3", "4", "5")).get();
+		Stream<Integer> s =
+				stream
+						.map(STRING_2_INTEGER)
+						.map(new Function<Integer, Integer>() {
+							int sum = 0;
+
+							@Override
+							public Integer apply(Integer i) {
+								sum += i;
+								return sum;
+							}
+						});
+		await(5, s, is(15));
+	}
+
+	@Test
+	public void testComposeFromMultipleFilteredValues() throws InterruptedException {
+		Stream<String> stream = Streams.defer(Arrays.asList("1", "2", "3", "4", "5")).get();
+		Stream<Integer> s =
+				stream
+						.map(STRING_2_INTEGER)
+						.filter(new Predicate<Integer>() {
+							@Override
+							public boolean test(Integer i) {
+								return i % 2 == 0;
+							}
+
+						});
+
+		await(2, s, is(4));
+	}
+
+	@Test
+	public void testComposedErrorHandlingWithMultipleValues() throws InterruptedException {
+		Stream<String> stream =
+				Streams.defer(Arrays.asList("1", "2", "3", "4", "5"))
+				       .env(env)
+				       .dispatcher("eventLoop")
+				       .get();
+
+		final AtomicBoolean exception = new AtomicBoolean(false);
+		Stream<Integer> s =
+				stream
+						.map(STRING_2_INTEGER)
+						.map(new Function<Integer, Integer>() {
+							int sum = 0;
+
+							@Override
+							public Integer apply(Integer i) {
+								if (i >= 5) {
+									throw new IllegalArgumentException();
+								}
+								sum += i;
+								return sum;
+							}
+						})
+						.when(IllegalArgumentException.class, new Consumer<IllegalArgumentException>() {
+							@Override
+							public void accept(IllegalArgumentException e) {
+								exception.set(true);
+							}
+						});
+
+		await(5, s, is(10));
+		assertThat("exception triggered", exception.get(), is(true));
+	}
+
+	@Test
+	public void testReduce() throws InterruptedException {
+		Stream<String> stream = Streams.defer(Arrays.asList("1", "2", "3", "4", "5")).get();
+		Stream<Integer> s =
+				stream
+						.map(STRING_2_INTEGER)
+						.reduce(new Function<Tuple2<Integer, Integer>, Integer>() {
+							@Override
+							public Integer apply(Tuple2<Integer, Integer> r) {
+								return r.getT1() * r.getT2();
+							}
+						}, 1);
+		await(5, s, is(120));
+	}
+
+	@Test
+	public void testFirstAndLast() throws InterruptedException {
+		Stream<String> stream = Streams.defer(Arrays.asList("1", "2", "3", "4", "5")).get();
+		Stream<Integer> s =
+				stream
+						.map(STRING_2_INTEGER);
+
+		Tap<Integer> first = s.first().tap();
+		Tap<Integer> last = s.last().tap();
+
+		s.flush();
+
+		assertThat("First is 1", first.get(), is(1));
+		assertThat("Last is 5", last.get(), is(5));
+	}
+
+	@Test
+	public void testRelaysEventsToReactor() throws InterruptedException {
+		Reactor r = Reactors.reactor().get();
+		Selector key = Selectors.$();
+
+		final CountDownLatch latch = new CountDownLatch(5);
+		r.on(key, new Consumer<Event<Integer>>() {
+			@Override
+			public void accept(Event<Integer> integerEvent) {
+				latch.countDown();
+			}
+		});
+
+		Stream<String> stream = Streams.defer(Arrays.asList("1", "2", "3", "4", "5")).get();
+		Stream<Integer> s =
+				stream
+						.map(STRING_2_INTEGER)
+						.consume(key.getObject(), r);
+		Tap<Integer> tap = s.tap();
+
+		s.flush(); // Trigger the deferred value to be set
+
+		//await(s, is(5));
+		assertThat("latch was counted down", latch.getCount(), is(0l));
+		assertThat("value is 5", tap.get(), is(5));
+	}
+
+	@Test
+	public void testStreamBatchesResults() {
+		Stream<String> stream = Streams.defer(Arrays.asList("1", "2", "3", "4", "5")).get();
+		Stream<List<Integer>> s =
+				stream
+						.map(STRING_2_INTEGER)
+						.collect();
+
+		final AtomicInteger batchCount = new AtomicInteger();
+		final AtomicInteger count = new AtomicInteger();
+		s.consume(new Consumer<List<Integer>>() {
+			@Override
+			public void accept(List<Integer> is) {
+				batchCount.incrementAndGet();
+				for (int i : is) {
+					count.addAndGet(i);
+				}
+			}
+		}).flush();
+
+		assertThat("batchCount is 3", batchCount.get(), is(1));
+		assertThat("count is 15", count.get(), is(15));
+	}
+
+	@Test
+	public void testHandlersErrorsDownstream() throws InterruptedException {
+		Stream<String> stream = Streams.defer(Arrays.asList("1", "2", "a", "4", "5")).get();
+		final CountDownLatch latch = new CountDownLatch(1);
+		Stream<Integer> s =
+				stream
+						.map(STRING_2_INTEGER)
+						.map(new Function<Integer, Integer>() {
+							int sum = 0;
+
+							@Override
+							public Integer apply(Integer i) {
+								if (i >= 5) {
+									throw new IllegalArgumentException();
+								}
+								sum += i;
+								return sum;
+							}
+						})
+						.when(NumberFormatException.class, new Consumer<NumberFormatException>() {
+							@Override
+							public void accept(NumberFormatException e) {
+								latch.countDown();
+							}
+						});
+
+		await(2, s, is(7));
+		assertThat("error handler was invoked", latch.getCount(), is(0L));
+	}
+
+	@Test
+	public void promiseAcceptCountCannotExceedOne() {
+		Deferred<Object, Promise<Object>> deferred = Promises.<Object>defer().get();
+		deferred.accept("alpha");
+		try {
+			deferred.accept("bravo");
+		} catch (IllegalStateException ise) {
+			// Swallow
+		}
+		assertEquals(deferred.compose().get(), "alpha");
+	}
+
+	@Test
+	public void promiseErrorCountCannotExceedOne() {
+		Deferred<Object, Promise<Object>> deferred = Promises.<Object>defer().get();
+		Throwable error = new Exception();
+		deferred.accept(error);
+		try {
+			deferred.accept(error);
+		} catch (IllegalStateException ise) {
+			// Swallow
+		}
+		assertTrue(deferred.compose().reason() instanceof Exception);
+	}
+
+	@Test
+	public void promiseAcceptCountAndErrorCountCannotExceedOneInTotal() {
+		Deferred<Object, Promise<Object>> deferred = Promises.<Object>defer().get();
+		Throwable error = new Exception();
+		deferred.accept(error);
+		try {
+			deferred.accept("alpha");
+		} catch (IllegalStateException ise) {
+			// Swallow
+		}
+		assertTrue(deferred.compose().reason() instanceof Exception);
+		try {
+			deferred.compose().get();
+			fail();
+		} catch (RuntimeException ise) {
+			assertEquals(deferred.compose().reason(), ise.getCause());
+		}
+	}
+
+	@Test
+	public void mapManyFlushesAllValuesThoroughly() throws InterruptedException {
+		int items = 30;
+		CountDownLatch latch = new CountDownLatch(items);
+		Random random = ThreadLocalRandom.current();
+
+		Deferred<String, Stream<String>> d = Streams.defer(env, Environment.RING_BUFFER);
+		Stream<Integer> tasks = d.compose()
+		                         .mapMany(s -> Promises.success(s)
+		                                               .env(env)
+		                                               .dispatcher(Environment.THREAD_POOL)
+		                                               .get()
+		                                               .<Integer>map(str -> {
+			                                               try {
+				                                               Thread.sleep(random.nextInt(500));
+			                                               } catch (InterruptedException e) {
+				                                               Thread.currentThread().interrupt();
+			                                               }
+			                                               return Integer.parseInt(str);
+		                                               }));
+
+		tasks.consume(i -> latch.countDown());
+
+		for (int i = 0; i < items; i++) {
+			d.accept(String.valueOf(i));
+		}
+
+		assertTrue(latch.getCount() + " of " + items + " items were counted down",
+		           latch.await(items, TimeUnit.SECONDS));
+	}
+
+	@Test
+	public void mapManyFlushesAllValuesConsistently() throws InterruptedException {
+		int iterations = 10;
+		for (int i = 0; i < iterations; i++) {
+			mapManyFlushesAllValuesThoroughly();
+		}
+	}
+
+	<T> void await(Stream<T> s, Matcher<T> expected) throws InterruptedException {
+		await(1, s, expected);
+	}
+
+	<T> void await(int count, Stream<T> s, Matcher<T> expected) throws InterruptedException {
+		final CountDownLatch latch = new CountDownLatch(count);
+		final AtomicReference<T> ref = new AtomicReference<T>();
+		s.consume(new Consumer<T>() {
+			@Override
+			public void accept(T t) {
+				ref.set(t);
+				latch.countDown();
+			}
+		}).when(Exception.class, new Consumer<Exception>() {
+			@Override
+			public void accept(Exception e) {
+				e.printStackTrace();
+				latch.countDown();
+			}
+		}).flush();
+
+		long startTime = System.currentTimeMillis();
+		T result = null;
+		try {
+			latch.await(1, TimeUnit.SECONDS);
+			result = ref.get();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		long duration = System.currentTimeMillis() - startTime;
+
+		System.out.println(s.debug());
+		assertThat(result, expected);
+		assertThat(duration, is(lessThan(2000L)));
+	}
+
+	/**
+	 * See #294 the consumer received more or less calls than expected Better reproducible with big thread pools, e.g. 128
+	 * threads
+	 *
+	 * @throws InterruptedException
+	 */
+	@Test
+	public void mapNotifiesOnce() throws InterruptedException {
+
+		final int COUNT = 5000;
+		final Object internalLock = new Object();
+		final Object consumerLock = new Object();
+
+		final CountDownLatch internalLatch = new CountDownLatch(COUNT);
+		final CountDownLatch counsumerLatch = new CountDownLatch(COUNT);
+
+		final AtomicInteger internalCounter = new AtomicInteger(0);
+		final AtomicInteger consumerCounter = new AtomicInteger(0);
+
+		final ConcurrentHashMap<Object, Long> seenInternal = new ConcurrentHashMap<>();
+		final ConcurrentHashMap<Object, Long> seenConsumer = new ConcurrentHashMap<>();
+
+		Environment e = new Environment();
+		Deferred<Integer, Stream<Integer>> d = Streams.defer(e, e.getDispatcher("workQueue"));
+
+		d.compose().map(new Function<Integer, Integer>() {
+			@Override
+			public Integer apply(Integer o) {
+				synchronized (internalLock) {
+
+					internalCounter.incrementAndGet();
+
+					long curThreadId = Thread.currentThread().getId();
+					Long prevThreadId = seenInternal.put(o, curThreadId);
+					if (prevThreadId != null) {
+						fail(String.format(
+								"The object %d has already been seen internally on the thread %d, current thread %d",
+								o, prevThreadId, curThreadId));
+					}
+
+					internalLatch.countDown();
+				}
+				return -o;
+			}
+		})
+		 .consume(new Consumer<Integer>() {
+			 @Override
+			 public void accept(Integer o) {
+				 synchronized (consumerLock) {
+					 consumerCounter.incrementAndGet();
+
+					 long curThreadId = Thread.currentThread().getId();
+					 Long prevThreadId = seenConsumer.put(o, curThreadId);
+					 if (prevThreadId != null) {
+						 System.out.println(String.format(
+								 "The object %d has already been seen by the consumer on the thread %d, current thread %d",
+								 o, prevThreadId, curThreadId));
+						 fail();
+					 }
+
+					 counsumerLatch.countDown();
+				 }
+			 }
+		 });
+
+		for (int i = 0; i < COUNT; i++) {
+			d.accept(i);
+		}
+
+		assertTrue(internalLatch.await(5, TimeUnit.SECONDS));
+		assertEquals(COUNT, internalCounter.get());
+		assertTrue(counsumerLatch.await(5, TimeUnit.SECONDS));
+		assertEquals(COUNT, consumerCounter.get());
+	}
+
+	static class String2Integer implements Function<String, Integer> {
+		@Override
+		public Integer apply(String s) {
+			return Integer.parseInt(s);
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/core/dynamic/DynamicReactorFactoryTests.java b/reactor-core/src/test/java/reactor/core/dynamic/DynamicReactorFactoryTests.java
new file mode 100644
index 0000000..f969134
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/core/dynamic/DynamicReactorFactoryTests.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic;
+
+import org.junit.Test;
+import reactor.AbstractReactorTest;
+import reactor.function.Consumer;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * @author Jon Brisbin
+ */
+public class DynamicReactorFactoryTests extends AbstractReactorTest {
+
+	@Test
+	public void testCreatesDynamicReactors() throws InterruptedException {
+		MyReactor myReactor = new DynamicReactorFactory<MyReactor>(env, MyReactor.class).create();
+
+		final CountDownLatch latch = new CountDownLatch(2);
+		myReactor.
+								 onTest(new Consumer<String>() {
+									 @Override
+									 public void accept(String s) {
+										 latch.countDown();
+									 }
+								 }).
+								 onTestTest(new Consumer<String>() {
+									 @Override
+									 public void accept(String s) {
+										 latch.countDown();
+									 }
+								 }).
+								 notifyTest("Hello World!").
+								 notifyTestTest("Hello World!");
+
+		latch.await(5, TimeUnit.SECONDS);
+
+		assertThat("Latch has been counted down", latch.getCount() == 0);
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/core/dynamic/MyReactor.java b/reactor-core/src/test/java/reactor/core/dynamic/MyReactor.java
new file mode 100644
index 0000000..420165d
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/core/dynamic/MyReactor.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.core.dynamic;
+
+import reactor.core.dynamic.annotation.Dispatcher;
+import reactor.core.dynamic.annotation.On;
+import reactor.function.Consumer;
+
+/**
+ * @author Jon Brisbin
+ */
+ at Dispatcher("threadPoolExecutor")
+public interface MyReactor extends DynamicReactor {
+
+	@On("test.test")
+	MyReactor onTestTest(Consumer<String> s);
+
+	MyReactor onTest(Consumer<String> s);
+
+	MyReactor notifyTest(String s);
+
+	MyReactor notifyTestTest(String s);
+
+}
diff --git a/reactor-core/src/test/java/reactor/core/fork/ForkJoinPoolTests.java b/reactor-core/src/test/java/reactor/core/fork/ForkJoinPoolTests.java
new file mode 100644
index 0000000..12dd73a
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/core/fork/ForkJoinPoolTests.java
@@ -0,0 +1,71 @@
+package reactor.core.fork;
+
+import com.gs.collections.api.list.ImmutableList;
+import com.gs.collections.impl.list.mutable.FastList;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.Environment;
+import reactor.core.composable.Promise;
+import reactor.function.Function;
+
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ForkJoinPoolTests {
+
+	Environment  env;
+	ForkJoinPool fjp;
+
+	@Before
+	public void setup() {
+		env = new Environment();
+		fjp = new ForkJoinPool(env);
+	}
+
+	@After
+	public void cleanup() {
+		env.shutdown();
+	}
+
+	@Test
+	public void forkJoinPoolJoinsTasks() throws InterruptedException {
+		int runs = 100;
+		Function<Void, Integer> task = new Function<Void, Integer>() {
+			final AtomicInteger count = new AtomicInteger(0);
+			final Random random = new Random(System.nanoTime());
+
+			@Override
+			public Integer apply(Void v) {
+				try {
+					Thread.sleep(random.nextInt(500));
+				} catch (InterruptedException e) {
+					e.printStackTrace();
+				}
+				System.out.println("thread=" + Thread.currentThread().getName());
+				return count.incrementAndGet();
+			}
+		};
+
+		List<Function<?, Integer>> tasks = FastList.newList();
+		for (int i = 0; i < runs; i++) {
+			tasks.add(task);
+		}
+
+		ForkJoinTask<ImmutableList<Integer>, Promise<ImmutableList<Integer>>> fjt = fjp.join(tasks);
+		fjt.submit();
+
+		ImmutableList<Integer> l = fjt.compose().await(15, TimeUnit.SECONDS);
+
+		assertThat("Integers were collected", l.size(), is(100));
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/core/processor/ProcessorThroughputTests.java b/reactor-core/src/test/java/reactor/core/processor/ProcessorThroughputTests.java
new file mode 100644
index 0000000..64dfa7c
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/core/processor/ProcessorThroughputTests.java
@@ -0,0 +1,147 @@
+package reactor.core.processor;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.core.processor.spec.ProcessorSpec;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+
+/**
+ * @author Jon Brisbin
+ */
+ at Ignore
+public class ProcessorThroughputTests {
+
+	static final int RUNS = 250000000;
+    static final int BATCH_SIZE = 512;
+
+    static final class Data {
+        String type;
+        Long   run;
+    }
+
+	private static final Supplier<Data> dataSupplier = new Supplier<Data>() {
+		@Override
+		public Data get() {
+			return new Data();
+		}
+	};
+
+    private static final Consumer<Data> dataConsumer = new Consumer<Data>() {
+        @Override
+        public void accept(Data data) {
+            data.type = "test";
+        }
+
+    };
+
+    private static final Consumer<Data> nullConsumer= new Consumer<Data>() {
+        @Override
+        public void accept(Data data) {
+        }
+    };
+
+	long           start;
+    String         waitStrategy;
+
+	@Before
+	public void setup() {
+	}
+
+	@After
+	public void cleanup() {
+		long end = System.currentTimeMillis();
+		long elapsed = (end - start);
+		long throughput = Math.round(RUNS / ((double) elapsed / 1000));
+		System.out.println(waitStrategy + " > elapsed: " + elapsed + "ms, throughput: " + throughput + "/sec");
+	}
+
+	@Test
+	public void testProcessorThroughput() throws InterruptedException {
+        Processor<Data> proc = new ProcessorSpec<Data>()
+            .dataSupplier(dataSupplier)
+            .consume(nullConsumer)
+            .blockingWaitStrategy()
+            .get();
+
+        waitStrategy = "default";
+        start = System.currentTimeMillis();
+
+		int runs = RUNS / BATCH_SIZE;
+
+		for (int i = 0; i < runs; i++) {
+			proc.batch(BATCH_SIZE, dataConsumer);
+		}
+	}
+
+    @Test
+    public void testBlockingWaitStrategyThroughput() throws InterruptedException {
+        Processor<Data> proc = new ProcessorSpec<Data>()
+            .dataSupplier(dataSupplier)
+            .consume(nullConsumer)
+            .blockingWaitStrategy()
+            .get();
+
+        waitStrategy = "blockingWaitStrategy";
+        start = System.currentTimeMillis();
+
+        int runs = RUNS / BATCH_SIZE;
+        for (int i = 0; i < runs; i++) {
+            proc.batch(BATCH_SIZE, dataConsumer);
+        }
+    }
+
+    @Test
+    public void testBusySpinWaitStrategyThroughput() throws InterruptedException {
+        Processor<Data> proc = new ProcessorSpec<Data>()
+            .dataSupplier(dataSupplier)
+            .consume(nullConsumer)
+            .busySpinWaitStrategy()
+            .get();
+
+        waitStrategy = "busySpinWaitStrategy";
+        start = System.currentTimeMillis();
+
+        int runs = RUNS / BATCH_SIZE;
+        for (int i = 0; i < runs; i++) {
+            proc.batch(BATCH_SIZE, dataConsumer);
+        }
+    }
+
+    @Test
+    public void testSleepingWaitStrategyThroughput() throws InterruptedException {
+        Processor<Data> proc = new ProcessorSpec<Data>()
+            .dataSupplier(dataSupplier)
+            .consume(nullConsumer)
+            .sleepingWaitStrategy()
+            .get();
+
+        waitStrategy = "sleepingWaitStrategy";
+        start = System.currentTimeMillis();
+
+        int runs = RUNS / BATCH_SIZE;
+        for (int i = 0; i < runs; i++) {
+            proc.batch(BATCH_SIZE, dataConsumer);
+        }
+    }
+
+    @Test
+    public void testYieldingWaitStrategyThroughput() throws InterruptedException {
+        Processor<Data> proc = new ProcessorSpec<Data>()
+            .dataSupplier(dataSupplier)
+            .consume(nullConsumer)
+            .yieldingWaitStrategy()
+            .get();
+
+        waitStrategy = "yieldingWaitStrategy";
+        start = System.currentTimeMillis();
+
+        int runs = RUNS / BATCH_SIZE;
+        for (int i = 0; i < runs; i++) {
+            proc.batch(BATCH_SIZE, dataConsumer);
+        }
+    }
+
+}
diff --git a/reactor-core/src/test/java/reactor/event/CachingAlgorithmTests.java b/reactor-core/src/test/java/reactor/event/CachingAlgorithmTests.java
new file mode 100644
index 0000000..876214c
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/event/CachingAlgorithmTests.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event;
+
+import org.junit.Test;
+import reactor.util.UUIDUtils;
+
+import java.util.*;
+
+/**
+ * @author Jon Brisbin
+ */
+public class CachingAlgorithmTests {
+
+	static int items      = 10000;
+	static int iterations = 500;
+
+	@Test
+	public void testUUIDGeneration() {
+		long start = System.currentTimeMillis();
+		for(int i = 0; i < items * iterations; i++) {
+			UUIDUtils.create();
+		}
+		long end = System.currentTimeMillis();
+		long elapsed = end - start;
+		long throughput = Math.round((items * iterations) / ((elapsed * 1.0) / 1000));
+
+		System.out.println("UUID generation throughput: " + throughput + "/sec");
+	}
+
+	@Test
+	public void testHashMapStringKeyCache() {
+		Map<String, List<?>> cache = new LinkedHashMap<String, List<?>>(items);
+
+		for(int i = 0; i < items; i++) {
+			List<Object> objs = new ArrayList<Object>();
+			for(int j = 0; j < 10; j++) {
+				objs.add(new Object());
+			}
+			cache.put("test" + i, objs);
+		}
+
+		long start = System.currentTimeMillis();
+		for(int i = 0; i < items * iterations; i++) {
+			String key = "test" + (i % items);
+			cache.get(key);
+		}
+		long end = System.currentTimeMillis();
+		long elapsed = end - start;
+		long throughput = Math.round((items * iterations) / ((elapsed * 1.0) / 1000));
+
+		System.out.println("LinkedHashMap<String,?> throughput: " + throughput + "/sec");
+	}
+
+	@Test
+	public void testHashMapLongKeyCache() {
+		Map<Long, List<?>> cache = new LinkedHashMap<Long, List<?>>(items);
+
+		for(long l = 0; l < items; l++) {
+			List<Object> objs = new ArrayList<Object>();
+			for(int j = 0; j < 10; j++) {
+				objs.add(new Object());
+			}
+			cache.put(l, objs);
+		}
+
+		long start = System.currentTimeMillis();
+		for(long l = 0; l < items * iterations; l++) {
+			cache.get(l % items);
+		}
+		long end = System.currentTimeMillis();
+		long elapsed = end - start;
+		long throughput = Math.round((items * iterations) / ((elapsed * 1.0) / 1000));
+
+		System.out.println("LinkedHashMap<Long,?> throughput: " + throughput + "/sec");
+	}
+
+	@Test
+	@SuppressWarnings({"unchecked", "rawtypes"})
+	public void testSlottedMapCache() {
+		int slots = 5000;
+		Map[] cache = new Map[slots];
+
+		for(long l = 0; l < items; l++) {
+			int s = (int)(l % slots);
+			if(null == cache[s]) {
+				cache[s] = new LinkedHashMap(items / slots);
+			}
+
+			List<Object> objs = new ArrayList<Object>();
+			for(int j = 0; j < 10; j++) {
+				objs.add(new Object());
+			}
+			cache[s].put(l, objs);
+		}
+
+		long start = System.currentTimeMillis();
+		for(long l = 0; l < items * iterations; l++) {
+			int j = (int)(l % items);
+			int x = (j % slots);
+			cache[x].get(j);
+		}
+		long end = System.currentTimeMillis();
+		long elapsed = end - start;
+		long throughput = Math.round((items * iterations) / ((elapsed * 1.0) / 1000));
+
+		System.out.println("Map[LinkedHashMap<Long,?>] throughput: " + throughput + "/sec");
+	}
+
+	@Test
+	public void testSlottedArraysCache() {
+		int slots = 256;
+		int layer2slots = slots / 2;
+		long[][][] cache = new long[slots][layer2slots][0];
+
+		Random random = new Random();
+		long[] ids = new long[items];
+
+		for(int i = 0; i < items; i++) {
+			ids[i] = random.nextLong();
+
+			int j = (int)Math.abs(ids[i] % slots);
+			int x = (i % layer2slots);
+
+			int len = cache[j][x].length;
+			if(len == 0) {
+				cache[j][x] = new long[]{ids[i]};
+			} else {
+				long[] entry = Arrays.copyOf(cache[j][x], len + 1);
+				entry[len] = ids[i];
+			}
+		}
+
+		long start = System.currentTimeMillis();
+		for(int i = 0; i < items * iterations; i++) {
+			long id = ids[(i % items)];
+			int j = (int)Math.abs(id % slots);
+			int x = (j % layer2slots);
+
+			int len = cache[j][x].length;
+			for(int idi = 0; idi < len; idi++) {
+				if(cache[j][x][idi] == id) {
+					break;
+				}
+			}
+		}
+		long end = System.currentTimeMillis();
+		long elapsed = end - start;
+		long throughput = Math.round((items * iterations) / ((elapsed * 1.0) / 1000));
+
+		System.out.println("long[][][] throughput: " + throughput + "/sec");
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/event/CachingRegistryTests.java b/reactor-core/src/test/java/reactor/event/CachingRegistryTests.java
new file mode 100644
index 0000000..f14ebdc
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/event/CachingRegistryTests.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event;
+
+import org.junit.Test;
+import reactor.event.registry.CachingRegistry;
+import reactor.event.registry.Registration;
+import reactor.event.registry.Registry;
+import reactor.event.selector.ObjectSelector;
+import reactor.event.selector.Selector;
+import reactor.event.selector.Selectors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+
+public final class CachingRegistryTests {
+
+	private final AtomicInteger    cacheMisses     = new AtomicInteger();
+	private final Registry<Object> cachingRegistry = new CacheMissCountingCachingRegistry<Object>(cacheMisses);
+
+	@Test
+	public void registrationsWithTheSameSelectorAreOrderedByInsertionOrder() {
+		String key = "selector";
+		Selector selector = Selectors.$(key);
+
+		this.cachingRegistry.register(selector, "echo");
+		this.cachingRegistry.register(selector, "bravo");
+		this.cachingRegistry.register(selector, "alpha");
+		this.cachingRegistry.register(selector, "charlie");
+		this.cachingRegistry.register(selector, "delta");
+
+		Iterable<Registration<? extends Object>> registrations = this.cachingRegistry.select(key);
+		List<Object> objects = new ArrayList<Object>();
+		for (Registration<? extends Object> registration : registrations) {
+			if (null != registration) {
+				objects.add(registration.getObject());
+			}
+		}
+
+		assertEquals(Arrays.asList("echo", "bravo", "alpha", "charlie", "delta"), objects);
+	}
+
+	@Test
+	public void nonEmptyResultsAreCached() {
+		String key = "/**/selector";
+		Selector selector = Selectors.uri(key);
+
+		this.cachingRegistry.register(selector, "alpha");
+
+		this.cachingRegistry.select("/test/selector");
+		this.cachingRegistry.select("/test/selector");
+
+		assertEquals(1, this.cacheMisses.get());
+	}
+
+	@Test
+	public void emptyResultsAreCached() {
+		this.cachingRegistry.register(Selectors.$("another-key"), "alpha");
+		this.cachingRegistry.select("key");
+		this.cachingRegistry.select("key");
+
+		assertEquals(1, this.cacheMisses.get());
+	}
+
+	@Test
+	public void emptyResultsAreCachedWhenThereAreNoRegistrations() {
+		this.cachingRegistry.select("key");
+		this.cachingRegistry.select("key");
+
+		assertEquals(1, this.cacheMisses.get());
+	}
+
+	@Test
+	public void cacheIsRefreshedWhenANewRegistrationWithTheSameSelectorIsMade() {
+		String key = "selector";
+		Selector selector = Selectors.$(key);
+
+		this.cachingRegistry.register(selector, "alpha");
+
+		this.cachingRegistry.select(key);
+		this.cachingRegistry.select(key);
+
+		assertEquals(1, this.cacheMisses.get());
+
+		this.cachingRegistry.register(selector, "bravo");
+
+		this.cachingRegistry.select(key);
+		this.cachingRegistry.select(key);
+
+		assertEquals(2, this.cacheMisses.get());
+	}
+
+	//@Test
+	public void cacheIsRefreshedWhenANewRegistrationWithADifferentSelectorIsMade() {
+		String key1 = "selector";
+		Selector selector1 = Selectors.$(key1);
+
+		this.cachingRegistry.register(selector1, "alpha");
+
+		this.cachingRegistry.select(key1);
+		this.cachingRegistry.select(key1);
+
+		assertEquals(1, this.cacheMisses.get());
+
+		String key2 = "selector2";
+		Selector selector2 = Selectors.$(key2);
+
+		this.cachingRegistry.register(selector2, "bravo");
+
+		this.cachingRegistry.select(key1);
+		this.cachingRegistry.select(key1);
+
+		assertEquals(2, this.cacheMisses.get());
+	}
+
+
+	//Issue : https://github.com/reactor/reactor/issues/237
+	@Test
+	public void invokeConsumersWithCustomSelector() {
+
+		Subscription sub1 = new Subscription("client1", "test");
+		Selector s1 = new MySelector(sub1);
+
+		// consumer1
+		this.cachingRegistry.register(s1, "pseudo-consumer-1");
+
+		// notify1
+		List<Registration<?>> registrations = this.cachingRegistry.select("test");
+
+		assertEquals( "number of consumers incorrect", 1, registrations.size());
+
+
+		// consumer2
+		this.cachingRegistry.register(s1, "pseudo-consumer-2");
+
+		// consumer3
+		Subscription sub2 = new Subscription("client2", "test");
+		Selector s2 = new MySelector(sub2);
+		this.cachingRegistry.register(s2, "pseudo-consumer-3");
+
+		//consumer 4
+		Subscription sub3 = new Subscription("client2", "test2");
+		Selector s3 = new MySelector(sub3);
+		this.cachingRegistry.register(s3, "pseudo-consumer-4");
+
+		//prepopulate and add another consumer
+		this.cachingRegistry.select("test2");
+		this.cachingRegistry.register(s3, "pseudo-consumer-5");
+
+		// notify2
+		registrations = this.cachingRegistry.select("test");
+		assertEquals( "number of consumers incorrect", 3, registrations.size());
+
+		registrations = this.cachingRegistry.select("test2");
+		assertEquals( "number of consumers incorrect", 2, registrations.size());
+
+
+		/*for(Registration<?> registration : registrations){
+			System.out.println (registration.getObject());
+		}*/
+	}
+
+	private static final class CacheMissCountingCachingRegistry<T> extends CachingRegistry<T> {
+		private final AtomicInteger cacheMisses;
+
+		public CacheMissCountingCachingRegistry(AtomicInteger cacheMisses) {
+			this.cacheMisses = cacheMisses;
+		}
+
+		@Override
+		protected void cacheMiss(Object key) {
+			this.cacheMisses.incrementAndGet();
+		}
+	}
+
+	public static class Subscription {
+		public final String clientId;
+		public final String topic;
+
+		public Subscription(String clientId, String topic) {
+			this.clientId = clientId;
+			this.topic = topic;
+		}
+	}
+
+	static class MySelector extends ObjectSelector<Subscription> {
+		public MySelector(Subscription subscription) {
+			super(subscription);
+		}
+
+		@Override
+		public boolean matches(Object key) {
+			if (!(key instanceof String)) {
+				return false;
+			}
+
+			return key.equals(getObject().topic);
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/event/SelectorUnitTests.java b/reactor-core/src/test/java/reactor/event/SelectorUnitTests.java
new file mode 100644
index 0000000..2a09afe
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/event/SelectorUnitTests.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.event;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.event.registry.CachingRegistry;
+import reactor.event.registry.Registration;
+import reactor.event.registry.Registry;
+import reactor.event.selector.Selector;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.tuple.Tuple;
+import reactor.tuple.Tuple2;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static reactor.io.selector.JsonPathSelector.J;
+import static reactor.event.selector.Selectors.$;
+import static reactor.event.selector.Selectors.U;
+
+/**
+ * @author Jon Brisbin
+ */
+public class SelectorUnitTests {
+
+	static final Logger LOG        = LoggerFactory.getLogger(SelectorUnitTests.class);
+	final        int    selectors  = 50;
+	final        int    iterations = 10000;
+
+	final ObjectMapper mapper = new ObjectMapper();
+
+	@Test
+	public void testSelectionThroughput() throws Exception {
+		runTest("ObjectSelector", new Function<Integer, Tuple2<Selector, Object>>() {
+			@Override
+			public Tuple2<Selector, Object> apply(Integer i) {
+				String key = "test" + i;
+				return Tuple.<Selector, Object>of($(key), key);
+			}
+		});
+	}
+
+	@Test
+	public void testUriTemplateSelectorThroughput() throws Exception {
+		runTest("UriPathSelector", new Function<Integer, Tuple2<Selector, Object>>() {
+			@Override
+			public Tuple2<Selector, Object> apply(Integer i) {
+				String key = "/test/" + i;
+				return Tuple.<Selector, Object>of(U(key), key);
+			}
+		});
+	}
+
+	@Test
+	public void testJsonPathSelectorThroughput() {
+		final String jsonTmpl = "{\"data\": [{\"run\": %s}]}";
+		final String jsonPathTmpl = "$.data[?(@.run == %s)]";
+
+		runTest("JsonPath[String]", new Function<Integer, Tuple2<Selector, Object>>() {
+			@Override
+			public Tuple2<Selector, Object> apply(Integer i) {
+				Selector sel = J(String.format(jsonPathTmpl, i));
+				String json = String.format(jsonTmpl, i);
+				return Tuple.<Selector, Object>of(sel, json);
+			}
+		});
+
+		runTest("JsonPath[POJO]", new Function<Integer, Tuple2<Selector, Object>>() {
+			@Override
+			public Tuple2<Selector, Object> apply(Integer i) {
+				Selector sel = J(String.format(jsonPathTmpl, i));
+				DataNode node = new DataNode(new DataNode.Run(i));
+				return Tuple.<Selector, Object>of(sel, node);
+			}
+		});
+
+		runTest("JsonPath[Map]", new Function<Integer, Tuple2<Selector, Object>>() {
+			@Override
+			public Tuple2<Selector, Object> apply(Integer i) {
+				Selector sel = J(String.format(jsonPathTmpl, i));
+				String json = String.format(jsonTmpl, i);
+				Object key;
+				try {
+					key = mapper.readValue(json, Map.class);
+				} catch(IOException e) {
+					throw new IllegalStateException(e);
+				}
+				return Tuple.<Selector, Object>of(sel, key);
+			}
+		});
+
+		runTest("JsonPath[Tree]", new Function<Integer, Tuple2<Selector, Object>>() {
+			@Override
+			public Tuple2<Selector, Object> apply(Integer i) {
+				Selector sel = J(String.format(jsonPathTmpl, i));
+				String json = String.format(jsonTmpl, i);
+				Object key;
+				try {
+					key = mapper.readTree(json);
+				} catch(IOException e) {
+					throw new IllegalStateException(e);
+				}
+				return Tuple.<Selector, Object>of(sel, key);
+			}
+		});
+	}
+
+	private void runTest(String type, Function<Integer, Tuple2<Selector, Object>> fn) {
+		final AtomicLong counter = new AtomicLong(selectors * iterations);
+		Registry<Consumer<?>> registry = new CachingRegistry<Consumer<?>>();
+
+		Consumer<?> countDown = new Consumer<Object>() {
+			@Override
+			public void accept(Object obj) {
+				counter.decrementAndGet();
+			}
+		};
+
+		Selector[] sels = new Selector[selectors];
+		Object[] keys = new Object[selectors];
+
+		for(int i = 0; i < selectors; i++) {
+			Tuple2<Selector, Object> tup = fn.apply(i);
+			sels[i] = tup.getT1();
+			keys[i] = tup.getT2();
+			registry.register(sels[i], countDown);
+		}
+
+		long start = System.currentTimeMillis();
+		for(int i = 0; i < selectors * iterations; i++) {
+			int j = i % selectors;
+			for(Registration<? extends Consumer<?>> reg : registry.select(keys[j])) {
+				reg.getObject().accept(null);
+			}
+		}
+		long end = System.currentTimeMillis();
+		double elapsed = (end - start);
+		long throughput = Math.round((selectors * iterations) / (elapsed / 1000));
+		LOG.info("{} throughput: {}M/s in {}ms", type, throughput, Math.round(elapsed));
+
+		assertThat("All handlers have been found and executed.", counter.get() == 0);
+	}
+
+	public static class DataNode {
+		private final List<Run> data = new ArrayList<Run>();
+
+		private DataNode(Run data) {
+			this.data.add(data);
+		}
+
+		public List<Run> getData() {
+			return data;
+		}
+
+		public static class Run {
+			private final int run;
+
+			Run(int run) {
+				this.run = run;
+			}
+
+			public int getRun() {
+				return run;
+			}
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/function/support/DelegatingConsumerTest.java b/reactor-core/src/test/java/reactor/function/support/DelegatingConsumerTest.java
new file mode 100644
index 0000000..364aa4f
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/function/support/DelegatingConsumerTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.function.support;
+
+import org.junit.Test;
+import reactor.function.batch.BatchConsumer;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Michael Nitschinger
+ */
+public class DelegatingConsumerTest {
+
+  @Test
+  public void testDelegatingBatchInformation() throws Exception {
+    DelegatingConsumer<Boolean> delegatingConsumer = new DelegatingConsumer<Boolean>();
+
+    final CountDownLatch latch = new CountDownLatch(3);
+    BatchConsumer<Boolean> batchConsumer = new BatchConsumer<Boolean>() {
+      @Override
+      public void start() {
+        latch.countDown();
+      }
+
+      @Override
+      public void end() {
+        latch.countDown();
+      }
+
+      @Override
+      public void accept(Boolean content) {
+        latch.countDown();
+      }
+    };
+
+    delegatingConsumer.add(batchConsumer);
+
+    delegatingConsumer.start();
+    delegatingConsumer.accept(true);
+    delegatingConsumer.end();
+
+    assertTrue("Batch consumer did not receive all methods", latch.await(5, TimeUnit.SECONDS));
+  }
+
+}
diff --git a/reactor-core/src/test/java/reactor/tuple/TupleTests.java b/reactor-core/src/test/java/reactor/tuple/TupleTests.java
new file mode 100644
index 0000000..97c3d77
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/tuple/TupleTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.tuple;
+
+import org.junit.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * @author Jon Brisbin
+ */
+public class TupleTests {
+
+	@Test
+	public void tupleHoldsArbitraryValues() {
+		Tuple t = Tuple.of("Hello World!");
+
+		assertThat("value is stored as an Object", t.get(0), is((Object) "Hello World!"));
+		assertThat("value is a String", String.class.isInstance(t.get(0)));
+	}
+
+	@Test
+	public void tupleProvidesTypeSafeMethods() {
+		Tuple3<String, Long, Integer> t3 = Tuple.of("string", 1L, 10);
+
+		assertThat("first value is a string", String.class.isInstance(t3.getT1()));
+		assertThat("second value is a long", Long.class.isInstance(t3.getT2()));
+		assertThat("third value is an int", Integer.class.isInstance(t3.getT3()));
+	}
+
+	@Test
+	public void tupleProvidesTupleTypeHierarchy() {
+		Tuple3<String, Long, Integer> t3 = Tuple.of("string", 1L, 10);
+
+		assertThat("Tuple3 is also a Tuple2", Tuple2.class.isInstance(t3));
+		assertThat("Tuple3 is also a Tuple1", Tuple1.class.isInstance(t3));
+	}
+
+	@Test
+	@SuppressWarnings({"rawtypes"})
+	public void tupleProvidesVirtuallyUnlimitedSize() {
+		TupleN tn = Tuple.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
+
+		assertThat("remaining values are in a Tuple", Tuple.class.isInstance(tn.getTRest()));
+		assertThat("last value in TRest is 15", (Integer) tn.getTRest().get(6), is(15));
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/util/AssertTests.java b/reactor-core/src/test/java/reactor/util/AssertTests.java
new file mode 100644
index 0000000..fe5165a
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/util/AssertTests.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Unit tests for the {@link Assert} class.
+ *
+ * @author Keith Donald
+ * @author Erwin Vervaet
+ * @author Rick Evans
+ * @author Arjen Poutsma
+ */
+public class AssertTests {
+
+	@Rule
+	public ExpectedException thrown = ExpectedException.none();
+
+	@Test(expected = IllegalArgumentException.class)
+	public void instanceOf() {
+		final Set<?> set = new HashSet<Object>();
+		Assert.isInstanceOf(HashSet.class, set);
+		Assert.isInstanceOf(HashMap.class, set);
+	}
+
+	@Test
+	public void instanceOfNoMessage() throws Exception {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("Object of class [java.lang.Object] must be an instance " +
+				"of interface java.util.Set");
+		Assert.isInstanceOf(Set.class, new Object(), null);
+	}
+
+	@Test
+	public void instanceOfMessage() throws Exception {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("Custom message. Object of class [java.lang.Object] must " +
+				"be an instance of interface java.util.Set");
+		Assert.isInstanceOf(Set.class, new Object(), "Custom message.");
+	}
+
+	@Test
+	public void isNullDoesNotThrowExceptionIfArgumentIsNullWithMessage() {
+		Assert.isNull(null, "Bla");
+	}
+
+	@Test
+	public void isNullDoesNotThrowExceptionIfArgumentIsNull() {
+		Assert.isNull(null);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void isNullThrowsExceptionIfArgumentIsNotNull() {
+		Assert.isNull(new Object());
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void isTrueWithFalseExpressionThrowsException() throws Exception {
+		Assert.isTrue(false);
+	}
+
+	@Test
+	public void isTrueWithTrueExpressionSunnyDay() throws Exception {
+		Assert.isTrue(true);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void testHasLengthWithNullStringThrowsException() throws Exception {
+		Assert.hasLength(null);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void hasLengthWithEmptyStringThrowsException() throws Exception {
+		Assert.hasLength("");
+	}
+
+	@Test
+	public void hasLengthWithWhitespaceOnlyStringDoesNotThrowException() throws Exception {
+		Assert.hasLength("\t  ");
+	}
+
+	@Test
+	public void hasLengthSunnyDay() throws Exception {
+		Assert.hasLength("I Heart ...");
+	}
+
+	@Test
+	public void doesNotContainWithNullSearchStringDoesNotThrowException() throws Exception {
+		Assert.doesNotContain(null, "rod");
+	}
+
+	@Test
+	public void doesNotContainWithNullSubstringDoesNotThrowException() throws Exception {
+		Assert.doesNotContain("A cool chick's name is Brod. ", null);
+	}
+
+	@Test
+	public void doesNotContainWithEmptySubstringDoesNotThrowException() throws Exception {
+		Assert.doesNotContain("A cool chick's name is Brod. ", "");
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void assertNotEmptyWithNullCollectionThrowsException() throws Exception {
+		Assert.notEmpty((Collection<?>) null);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void assertNotEmptyWithEmptyCollectionThrowsException() throws Exception {
+		Assert.notEmpty(new ArrayList<Object>());
+	}
+
+	@Test
+	public void assertNotEmptyWithCollectionSunnyDay() throws Exception {
+		List<String> collection = new ArrayList<String>();
+		collection.add("");
+		Assert.notEmpty(collection);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void assertNotEmptyWithNullMapThrowsException() throws Exception {
+		Assert.notEmpty((Map<?, ?>) null);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void assertNotEmptyWithEmptyMapThrowsException() throws Exception {
+		Assert.notEmpty(new HashMap<Object, Object>());
+	}
+
+	@Test
+	public void assertNotEmptyWithMapSunnyDay() throws Exception {
+		Map<String, String> map = new HashMap<String, String>();
+		map.put("", "");
+		Assert.notEmpty(map);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void isInstanceofClassWithNullInstanceThrowsException() throws Exception {
+		Assert.isInstanceOf(String.class, null);
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void stateWithFalseExpressionThrowsException() throws Exception {
+		Assert.state(false);
+	}
+
+	@Test
+	public void stateWithTrueExpressionSunnyDay() throws Exception {
+		Assert.state(true);
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/test/java/reactor/util/CollectionUtilsTests.java b/reactor-core/src/test/java/reactor/util/CollectionUtilsTests.java
new file mode 100644
index 0000000..b04d915
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/util/CollectionUtilsTests.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+/**
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @author Rick Evans
+ */
+public class CollectionUtilsTests {
+
+	@Test
+	public void testIsEmpty() {
+		assertTrue(CollectionUtils.isEmpty((Set<Object>) null));
+		assertTrue(CollectionUtils.isEmpty((Map<String, String>) null));
+		assertTrue(CollectionUtils.isEmpty(new HashMap<String, String>()));
+		assertTrue(CollectionUtils.isEmpty(new HashSet<Object>()));
+
+		List<Object> list = new LinkedList<Object>();
+		list.add(new Object());
+		assertFalse(CollectionUtils.isEmpty(list));
+
+		Map<String, String> map = new HashMap<String, String>();
+		map.put("foo", "bar");
+		assertFalse(CollectionUtils.isEmpty(map));
+	}
+
+	@Test
+	public void testMergeArrayIntoCollection() {
+		Object[] arr = new Object[] {"value1", "value2"};
+		List<Comparable<?>> list = new LinkedList<Comparable<?>>();
+		list.add("value3");
+
+		CollectionUtils.mergeArrayIntoCollection(arr, list);
+		assertEquals("value3", list.get(0));
+		assertEquals("value1", list.get(1));
+		assertEquals("value2", list.get(2));
+	}
+
+	@Test
+	public void testMergePrimitiveArrayIntoCollection() {
+		int[] arr = new int[] {1, 2};
+		List<Comparable<?>> list = new LinkedList<Comparable<?>>();
+		list.add(new Integer(3));
+
+		CollectionUtils.mergeArrayIntoCollection(arr, list);
+		assertEquals(new Integer(3), list.get(0));
+		assertEquals(new Integer(1), list.get(1));
+		assertEquals(new Integer(2), list.get(2));
+	}
+
+	@Test
+	public void testMergePropertiesIntoMap() {
+		Properties defaults = new Properties();
+		defaults.setProperty("prop1", "value1");
+		Properties props = new Properties(defaults);
+		props.setProperty("prop2", "value2");
+		props.put("prop3", new Integer(3));
+
+		Map<String, String> map = new HashMap<String, String>();
+		map.put("prop4", "value4");
+
+		CollectionUtils.mergePropertiesIntoMap(props, map);
+		assertEquals("value1", map.get("prop1"));
+		assertEquals("value2", map.get("prop2"));
+		assertEquals(new Integer(3), map.get("prop3"));
+		assertEquals("value4", map.get("prop4"));
+	}
+
+	@Test
+	public void testContains() {
+		assertFalse(CollectionUtils.contains((Iterator<String>) null, "myElement"));
+		assertFalse(CollectionUtils.contains((Enumeration<String>) null, "myElement"));
+		assertFalse(CollectionUtils.contains(new LinkedList<String>().iterator(), "myElement"));
+		assertFalse(CollectionUtils.contains(new Hashtable<String, Object>().keys(), "myElement"));
+
+		List<String> list = new LinkedList<String>();
+		list.add("myElement");
+		assertTrue(CollectionUtils.contains(list.iterator(), "myElement"));
+
+		Hashtable<String, String> ht = new Hashtable<String, String>();
+		ht.put("myElement", "myValue");
+		assertTrue(CollectionUtils.contains(ht.keys(), "myElement"));
+	}
+
+	@Test
+	public void testContainsAny() throws Exception {
+		List<String> source = new ArrayList<String>();
+		source.add("abc");
+		source.add("def");
+		source.add("ghi");
+
+		List<String> candidates = new ArrayList<String>();
+		candidates.add("xyz");
+		candidates.add("def");
+		candidates.add("abc");
+
+		assertTrue(CollectionUtils.containsAny(source, candidates));
+		candidates.remove("def");
+		assertTrue(CollectionUtils.containsAny(source, candidates));
+		candidates.remove("abc");
+		assertFalse(CollectionUtils.containsAny(source, candidates));
+	}
+
+	@Test
+	public void testContainsInstanceWithNullCollection() throws Exception {
+		assertFalse("Must return false if supplied Collection argument is null",
+				CollectionUtils.containsInstance(null, this));
+	}
+
+	@Test
+	public void testContainsInstanceWithInstancesThatAreEqualButDistinct() throws Exception {
+		List<Instance> list = new ArrayList<Instance>();
+		list.add(new Instance("fiona"));
+		assertFalse("Must return false if instance is not in the supplied Collection argument",
+				CollectionUtils.containsInstance(list, new Instance("fiona")));
+	}
+
+	@Test
+	public void testContainsInstanceWithSameInstance() throws Exception {
+		List<Instance> list = new ArrayList<Instance>();
+		list.add(new Instance("apple"));
+		Instance instance = new Instance("fiona");
+		list.add(instance);
+		assertTrue("Must return true if instance is in the supplied Collection argument",
+				CollectionUtils.containsInstance(list, instance));
+	}
+
+	@Test
+	public void testContainsInstanceWithNullInstance() throws Exception {
+		List<Instance> list = new ArrayList<Instance>();
+		list.add(new Instance("apple"));
+		list.add(new Instance("fiona"));
+		assertFalse("Must return false if null instance is supplied",
+				CollectionUtils.containsInstance(list, null));
+	}
+
+	@Test
+	public void testFindFirstMatch() throws Exception {
+		List<String> source = new ArrayList<String>();
+		source.add("abc");
+		source.add("def");
+		source.add("ghi");
+
+		List<String> candidates = new ArrayList<String>();
+		candidates.add("xyz");
+		candidates.add("def");
+		candidates.add("abc");
+
+		assertEquals("def", CollectionUtils.findFirstMatch(source, candidates));
+	}
+
+	@Test
+	public void testHasUniqueObject() {
+		List<String> list = new LinkedList<String>();
+		list.add("myElement");
+		list.add("myOtherElement");
+		assertFalse(CollectionUtils.hasUniqueObject(list));
+
+		list = new LinkedList<String>();
+		list.add("myElement");
+		assertTrue(CollectionUtils.hasUniqueObject(list));
+
+		list = new LinkedList<String>();
+		list.add("myElement");
+		list.add(null);
+		assertFalse(CollectionUtils.hasUniqueObject(list));
+
+		list = new LinkedList<String>();
+		list.add(null);
+		list.add("myElement");
+		assertFalse(CollectionUtils.hasUniqueObject(list));
+
+		list = new LinkedList<String>();
+		list.add(null);
+		list.add(null);
+		assertTrue(CollectionUtils.hasUniqueObject(list));
+
+		list = new LinkedList<String>();
+		list.add(null);
+		assertTrue(CollectionUtils.hasUniqueObject(list));
+
+		list = new LinkedList<String>();
+		assertFalse(CollectionUtils.hasUniqueObject(list));
+	}
+
+
+	private static final class Instance {
+
+		private final String name;
+
+		public Instance(String name) {
+			this.name = name;
+		}
+
+		@Override
+		public boolean equals(Object rhs) {
+			if (this == rhs) {
+				return true;
+			}
+			if (rhs == null || this.getClass() != rhs.getClass()) {
+				return false;
+			}
+			Instance instance = (Instance) rhs;
+			return this.name.equals(instance.name);
+		}
+
+		@Override
+		public int hashCode() {
+			return this.name.hashCode();
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/test/java/reactor/util/LinkedCaseInsensitiveMapTests.java b/reactor-core/src/test/java/reactor/util/LinkedCaseInsensitiveMapTests.java
new file mode 100644
index 0000000..8122d5b
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/util/LinkedCaseInsensitiveMapTests.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Juergen Hoeller
+ */
+public class LinkedCaseInsensitiveMapTests {
+
+	private LinkedCaseInsensitiveMap<String> map;
+
+	@Before
+	public void setUp() {
+		map = new LinkedCaseInsensitiveMap<String>();
+	}
+
+	@Test
+	public void putAndGet() {
+		map.put("key", "value1");
+		map.put("key", "value2");
+		map.put("key", "value3");
+		assertEquals(1, map.size());
+		assertEquals("value3", map.get("key"));
+		assertEquals("value3", map.get("KEY"));
+		assertEquals("value3", map.get("Key"));
+	}
+
+	@Test
+	public void putWithOverlappingKeys() {
+		map.put("key", "value1");
+		map.put("KEY", "value2");
+		map.put("Key", "value3");
+		assertEquals(1, map.size());
+		assertEquals("value3", map.get("key"));
+		assertEquals("value3", map.get("KEY"));
+		assertEquals("value3", map.get("Key"));
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/test/java/reactor/util/LinkedMultiValueMapTests.java b/reactor-core/src/test/java/reactor/util/LinkedMultiValueMapTests.java
new file mode 100644
index 0000000..13272c2
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/util/LinkedMultiValueMapTests.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Arjen Poutsma
+ */
+public class LinkedMultiValueMapTests {
+
+	private LinkedMultiValueMap<String, String> map;
+
+	@Before
+	public void setUp() {
+		map = new LinkedMultiValueMap<String, String>();
+	}
+
+	@Test
+	public void add() {
+		map.add("key", "value1");
+		map.add("key", "value2");
+		assertEquals(1, map.size());
+		List<String> expected = new ArrayList<String>(2);
+		expected.add("value1");
+		expected.add("value2");
+		assertEquals(expected, map.get("key"));
+	}
+
+	@Test
+	public void getFirst() {
+		List<String> values = new ArrayList<String>(2);
+		values.add("value1");
+		values.add("value2");
+		map.put("key", values);
+		assertEquals("value1", map.getFirst("key"));
+		assertNull(map.getFirst("other"));
+	}
+
+	@Test
+	public void set() {
+		map.set("key", "value1");
+		map.set("key", "value2");
+		assertEquals(1, map.size());
+		assertEquals(Collections.singletonList("value2"), map.get("key"));
+	}
+
+	@Test
+	public void equals() {
+		map.set("key1", "value1");
+		assertEquals(map, map);
+		MultiValueMap<String, String> o1 = new LinkedMultiValueMap<String, String>();
+		o1.set("key1", "value1");
+		assertEquals(map, o1);
+		assertEquals(o1, map);
+		Map<String, List<String>> o2 = new HashMap<String, List<String>>();
+		o2.put("key1", Collections.singletonList("value1"));
+		assertEquals(map, o2);
+		assertEquals(o2, map);
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/test/java/reactor/util/ObjectUtilsTests.java b/reactor-core/src/test/java/reactor/util/ObjectUtilsTests.java
new file mode 100644
index 0000000..49e5924
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/util/ObjectUtilsTests.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.sql.SQLException;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Rick Evans
+ */
+public final class ObjectUtilsTests extends TestCase {
+
+	public void testIsCheckedException() {
+		assertTrue(ObjectUtils.isCheckedException(new Exception()));
+		assertTrue(ObjectUtils.isCheckedException(new SQLException()));
+
+		assertFalse(ObjectUtils.isCheckedException(new RuntimeException()));
+
+		// Any Throwable other than RuntimeException and Error
+		// has to be considered checked according to the JLS.
+		assertTrue(ObjectUtils.isCheckedException(new Throwable()));
+	}
+
+	public void testIsCompatibleWithThrowsClause() {
+		Class<?>[] empty = new Class[0];
+		Class<?>[] exception = new Class[] {Exception.class};
+		Class<?>[] sqlAndIO = new Class[] {SQLException.class, IOException.class};
+		Class<?>[] throwable = new Class[] {Throwable.class};
+
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), null));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), empty));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), exception));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), sqlAndIO));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), throwable));
+
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new Exception(), null));
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new Exception(), empty));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new Exception(), exception));
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new Exception(), sqlAndIO));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new Exception(), throwable));
+
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new SQLException(), null));
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new SQLException(), empty));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new SQLException(), exception));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new SQLException(), sqlAndIO));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new SQLException(), throwable));
+
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new Throwable(), null));
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new Throwable(), empty));
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new Throwable(), exception));
+		assertFalse(ObjectUtils.isCompatibleWithThrowsClause(new Throwable(), sqlAndIO));
+		assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new Throwable(), throwable));
+	}
+
+	public void testToObjectArray() {
+		int[] a = new int[] {1, 2, 3, 4, 5};
+		Integer[] wrapper = (Integer[]) ObjectUtils.toObjectArray(a);
+		assertTrue(wrapper.length == 5);
+		for (int i = 0; i < wrapper.length; i++) {
+			assertEquals(a[i], wrapper[i].intValue());
+		}
+	}
+
+	public void testToObjectArrayWithNull() {
+		Object[] objects = ObjectUtils.toObjectArray(null);
+		assertNotNull(objects);
+		assertEquals(0, objects.length);
+	}
+
+	public void testToObjectArrayWithEmptyPrimitiveArray() {
+		Object[] objects = ObjectUtils.toObjectArray(new byte[] {});
+		assertNotNull(objects);
+		assertEquals(0, objects.length);
+	}
+
+	public void testToObjectArrayWithNonArrayType() {
+		try {
+			ObjectUtils.toObjectArray("Not an []");
+			fail("Must have thrown an IllegalArgumentException by this point.");
+		}
+		catch (IllegalArgumentException expected) {
+		}
+	}
+
+	public void testToObjectArrayWithNonPrimitiveArray() {
+		String[] source = new String[] {"Bingo"};
+		assertEquals(source, ObjectUtils.toObjectArray(source));
+	}
+
+	public void testAddObjectToArraySunnyDay() {
+		String[] array = new String[] {"foo", "bar"};
+		String newElement = "baz";
+		Object[] newArray = ObjectUtils.addObjectToArray(array, newElement);
+		assertEquals(3, newArray.length);
+		assertEquals(newElement, newArray[2]);
+	}
+
+	public void testAddObjectToArrayWhenEmpty() {
+		String[] array = new String[0];
+		String newElement = "foo";
+		String[] newArray = ObjectUtils.addObjectToArray(array, newElement);
+		assertEquals(1, newArray.length);
+		assertEquals(newElement, newArray[0]);
+	}
+
+	public void testAddObjectToSingleNonNullElementArray() {
+		String existingElement = "foo";
+		String[] array = new String[] {existingElement};
+		String newElement = "bar";
+		String[] newArray = ObjectUtils.addObjectToArray(array, newElement);
+		assertEquals(2, newArray.length);
+		assertEquals(existingElement, newArray[0]);
+		assertEquals(newElement, newArray[1]);
+	}
+
+	public void testAddObjectToSingleNullElementArray() {
+		String[] array = new String[] {null};
+		String newElement = "bar";
+		String[] newArray = ObjectUtils.addObjectToArray(array, newElement);
+		assertEquals(2, newArray.length);
+		assertEquals(null, newArray[0]);
+		assertEquals(newElement, newArray[1]);
+	}
+
+	public void testAddObjectToNullArray() throws Exception {
+		String newElement = "foo";
+		String[] newArray = ObjectUtils.addObjectToArray(null, newElement);
+		assertEquals(1, newArray.length);
+		assertEquals(newElement, newArray[0]);
+	}
+
+	public void testAddNullObjectToNullArray() throws Exception {
+		Object[] newArray = ObjectUtils.addObjectToArray(null, null);
+		assertEquals(1, newArray.length);
+		assertEquals(null, newArray[0]);
+	}
+
+	public void testNullSafeEqualsWithArrays() throws Exception {
+		assertTrue(ObjectUtils.nullSafeEquals(new String[] {"a", "b", "c"}, new String[] {"a", "b", "c"}));
+		assertTrue(ObjectUtils.nullSafeEquals(new int[] {1, 2, 3}, new int[] {1, 2, 3}));
+	}
+
+	public void testHashCodeWithBooleanFalse() {
+		int expected = Boolean.FALSE.hashCode();
+		assertEquals(expected, ObjectUtils.hashCode(false));
+	}
+
+	public void testHashCodeWithBooleanTrue() {
+		int expected = Boolean.TRUE.hashCode();
+		assertEquals(expected, ObjectUtils.hashCode(true));
+	}
+
+	public void testHashCodeWithDouble() {
+		double dbl = 9830.43;
+		int expected = (new Double(dbl)).hashCode();
+		assertEquals(expected, ObjectUtils.hashCode(dbl));
+	}
+
+	public void testHashCodeWithFloat() {
+		float flt = 34.8f;
+		int expected = (new Float(flt)).hashCode();
+		assertEquals(expected, ObjectUtils.hashCode(flt));
+	}
+
+	public void testHashCodeWithLong() {
+		long lng = 883l;
+		int expected = (new Long(lng)).hashCode();
+		assertEquals(expected, ObjectUtils.hashCode(lng));
+	}
+
+	public void testIdentityToString() {
+		Object obj = new Object();
+		String expected = obj.getClass().getName() + "@" + ObjectUtils.getIdentityHexString(obj);
+		String actual = ObjectUtils.identityToString(obj);
+		assertEquals(expected.toString(), actual);
+	}
+
+	public void testIdentityToStringWithNullObject() {
+		assertEquals("", ObjectUtils.identityToString(null));
+	}
+
+	public void testNullSafeHashCodeWithBooleanArray() {
+		int expected = 31 * 7 + Boolean.TRUE.hashCode();
+		expected = 31 * expected + Boolean.FALSE.hashCode();
+
+		boolean[] array = {true, false};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithBooleanArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((boolean[]) null));
+	}
+
+	public void testNullSafeHashCodeWithByteArray() {
+		int expected = 31 * 7 + 8;
+		expected = 31 * expected + 10;
+
+		byte[] array = {8, 10};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithByteArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((byte[]) null));
+	}
+
+	public void testNullSafeHashCodeWithCharArray() {
+		int expected = 31 * 7 + 'a';
+		expected = 31 * expected + 'E';
+
+		char[] array = {'a', 'E'};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithCharArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((char[]) null));
+	}
+
+	public void testNullSafeHashCodeWithDoubleArray() {
+		long bits = Double.doubleToLongBits(8449.65);
+		int expected = 31 * 7 + (int) (bits ^ (bits >>> 32));
+		bits = Double.doubleToLongBits(9944.923);
+		expected = 31 * expected + (int) (bits ^ (bits >>> 32));
+
+		double[] array = {8449.65, 9944.923};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithDoubleArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((double[]) null));
+	}
+
+	public void testNullSafeHashCodeWithFloatArray() {
+		int expected = 31 * 7 + Float.floatToIntBits(9.6f);
+		expected = 31 * expected + Float.floatToIntBits(7.4f);
+
+		float[] array = {9.6f, 7.4f};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithFloatArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((float[]) null));
+	}
+
+	public void testNullSafeHashCodeWithIntArray() {
+		int expected = 31 * 7 + 884;
+		expected = 31 * expected + 340;
+
+		int[] array = {884, 340};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithIntArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((int[]) null));
+	}
+
+	public void testNullSafeHashCodeWithLongArray() {
+		long lng = 7993l;
+		int expected = 31 * 7 + (int) (lng ^ (lng >>> 32));
+		lng = 84320l;
+		expected = 31 * expected + (int) (lng ^ (lng >>> 32));
+
+		long[] array = {7993l, 84320l};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithLongArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((long[]) null));
+	}
+
+	public void testNullSafeHashCodeWithObject() {
+		String str = "Luke";
+		assertEquals(str.hashCode(), ObjectUtils.nullSafeHashCode(str));
+	}
+
+	public void testNullSafeHashCodeWithObjectArray() {
+		int expected = 31 * 7 + "Leia".hashCode();
+		expected = 31 * expected + "Han".hashCode();
+
+		Object[] array = {"Leia", "Han"};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithObjectArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((Object[]) null));
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingBooleanArray() {
+		Object array = new boolean[] {true, false};
+		int expected = ObjectUtils.nullSafeHashCode((boolean[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingByteArray() {
+		Object array = new byte[] {6, 39};
+		int expected = ObjectUtils.nullSafeHashCode((byte[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingCharArray() {
+		Object array = new char[] {'l', 'M'};
+		int expected = ObjectUtils.nullSafeHashCode((char[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingDoubleArray() {
+		Object array = new double[] {68930.993, 9022.009};
+		int expected = ObjectUtils.nullSafeHashCode((double[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingFloatArray() {
+		Object array = new float[] {9.9f, 9.54f};
+		int expected = ObjectUtils.nullSafeHashCode((float[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingIntArray() {
+		Object array = new int[] {89, 32};
+		int expected = ObjectUtils.nullSafeHashCode((int[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingLongArray() {
+		Object array = new long[] {4389, 320};
+		int expected = ObjectUtils.nullSafeHashCode((long[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingObjectArray() {
+		Object array = new Object[] {"Luke", "Anakin"};
+		int expected = ObjectUtils.nullSafeHashCode((Object[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectBeingShortArray() {
+		Object array = new short[] {5, 3};
+		int expected = ObjectUtils.nullSafeHashCode((short[]) array);
+		assertEqualHashCodes(expected, array);
+	}
+
+	public void testNullSafeHashCodeWithObjectEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((Object) null));
+	}
+
+	public void testNullSafeHashCodeWithShortArray() {
+		int expected = 31 * 7 + 70;
+		expected = 31 * expected + 8;
+
+		short[] array = {70, 8};
+		int actual = ObjectUtils.nullSafeHashCode(array);
+
+		assertEquals(expected, actual);
+	}
+
+	public void testNullSafeHashCodeWithShortArrayEqualToNull() {
+		assertEquals(0, ObjectUtils.nullSafeHashCode((short[]) null));
+	}
+
+	public void testNullSafeToStringWithBooleanArray() {
+		boolean[] array = {true, false};
+		assertEquals("{true, false}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithBooleanArrayBeingEmpty() {
+		boolean[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithBooleanArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((boolean[]) null));
+	}
+
+	public void testNullSafeToStringWithByteArray() {
+		byte[] array = {5, 8};
+		assertEquals("{5, 8}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithByteArrayBeingEmpty() {
+		byte[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithByteArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((byte[]) null));
+	}
+
+	public void testNullSafeToStringWithCharArray() {
+		char[] array = {'A', 'B'};
+		assertEquals("{'A', 'B'}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithCharArrayBeingEmpty() {
+		char[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithCharArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((char[]) null));
+	}
+
+	public void testNullSafeToStringWithDoubleArray() {
+		double[] array = {8594.93, 8594023.95};
+		assertEquals("{8594.93, 8594023.95}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithDoubleArrayBeingEmpty() {
+		double[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithDoubleArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((double[]) null));
+	}
+
+	public void testNullSafeToStringWithFloatArray() {
+		float[] array = {8.6f, 43.8f};
+		assertEquals("{8.6, 43.8}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithFloatArrayBeingEmpty() {
+		float[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithFloatArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((float[]) null));
+	}
+
+	public void testNullSafeToStringWithIntArray() {
+		int[] array = {9, 64};
+		assertEquals("{9, 64}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithIntArrayBeingEmpty() {
+		int[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithIntArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((int[]) null));
+	}
+
+	public void testNullSafeToStringWithLongArray() {
+		long[] array = {434l, 23423l};
+		assertEquals("{434, 23423}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithLongArrayBeingEmpty() {
+		long[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithLongArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((long[]) null));
+	}
+
+	public void testNullSafeToStringWithPlainOldString() {
+		assertEquals("I shoh love tha taste of mangoes", ObjectUtils.nullSafeToString("I shoh love tha taste of mangoes"));
+	}
+
+	public void testNullSafeToStringWithObjectArray() {
+		Object[] array = {"Han", new Long(43)};
+		assertEquals("{Han, 43}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithObjectArrayBeingEmpty() {
+		Object[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithObjectArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((Object[]) null));
+	}
+
+	public void testNullSafeToStringWithShortArray() {
+		short[] array = {7, 9};
+		assertEquals("{7, 9}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithShortArrayBeingEmpty() {
+		short[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithShortArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((short[]) null));
+	}
+
+	public void testNullSafeToStringWithStringArray() {
+		String[] array = {"Luke", "Anakin"};
+		assertEquals("{Luke, Anakin}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithStringArrayBeingEmpty() {
+		String[] array = {};
+		assertEquals("{}", ObjectUtils.nullSafeToString(array));
+	}
+
+	public void testNullSafeToStringWithStringArrayEqualToNull() {
+		assertEquals("null", ObjectUtils.nullSafeToString((String[]) null));
+	}
+
+	enum Tropes { FOO, BAR, baz };
+
+	public void testContainsConstant() {
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "FOO"), is(true));
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "foo"), is(true));
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "BaR"), is(true));
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "bar"), is(true));
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "BAZ"), is(true));
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "baz"), is(true));
+
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "BOGUS"), is(false));
+
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "FOO", true), is(true));
+		assertThat(ObjectUtils.containsConstant(Tropes.values(), "foo", true), is(false));
+	}
+
+	public void testCaseInsensitiveValueOf() {
+		assertThat(ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "foo"), is(Tropes.FOO));
+		assertThat(ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "BAR"), is(Tropes.BAR));
+		try {
+			ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "bogus");
+			fail("expected IllegalArgumentException");
+		} catch (IllegalArgumentException ex) {
+			assertThat(ex.getMessage(),
+					is("constant [bogus] does not exist in enum type " +
+					"reactor.util.ObjectUtilsTests$Tropes"));
+		}
+	}
+
+	private void assertEqualHashCodes(int expected, Object array) {
+		int actual = ObjectUtils.nullSafeHashCode(array);
+		assertEquals(expected, actual);
+		assertTrue(array.hashCode() != actual);
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-core/src/test/java/reactor/util/PartitionedReferencePileTests.java b/reactor-core/src/test/java/reactor/util/PartitionedReferencePileTests.java
new file mode 100644
index 0000000..6695669
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/util/PartitionedReferencePileTests.java
@@ -0,0 +1,136 @@
+package reactor.util;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.*;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.iterableWithSize;
+
+/**
+ * @author Jon Brisbin
+ */
+public class PartitionedReferencePileTests {
+
+	int threadCnt = 8;
+
+	ExecutorService pool;
+	Timer           timer;
+
+	volatile long now;
+
+	@Before
+	public void setup() {
+		pool = Executors.newFixedThreadPool(threadCnt);
+		timer = new Timer(true);
+
+		timer.scheduleAtFixedRate(new TimerTask() {
+			@Override
+			public void run() {
+				now = System.currentTimeMillis();
+			}
+		}, 0, 200);
+	}
+
+	@Test
+	public void partitionedReferencePileProvidesThreadLocal() throws InterruptedException {
+		int backlog = 32;
+
+		PartitionedReferencePile<Slot> pile = new PartitionedReferencePile<Slot>(backlog, Slot::new);
+		CountDownLatch latch = new CountDownLatch(threadCnt);
+
+		for (int i = 0; i < threadCnt; i++) {
+			int idx = i;
+			pool.submit(() -> {
+				for (int j = 0; j < backlog * 2; j++) {
+					Slot s = pile.get();
+					s.var0 = idx;
+					s.var1 = j;
+				}
+
+				int expected = 0;
+				for (Slot s : pile) {
+					assert s.var1 == expected++;
+				}
+
+				latch.countDown();
+			});
+		}
+
+		assertThat("Latch was counted down", latch.await(5, TimeUnit.SECONDS));
+	}
+
+	@Test
+	public void partitionedReferencePileCollectsAllObjects() throws InterruptedException, ExecutionException {
+		int backlog = 32;
+
+		PartitionedReferencePile<Slot> pile = new PartitionedReferencePile<Slot>(backlog, Slot::new);
+
+		Future[] futures = new Future[threadCnt];
+		for (int i = 0; i < threadCnt; i++) {
+			int idx = i;
+			futures[i] = pool.submit(() -> {
+				for (int j = 0; j < backlog * 2; j++) {
+					Slot s = pile.get();
+					s.var0 = idx;
+					s.var1 = j;
+				}
+			});
+		}
+		for (Future f : futures) {
+			f.get();
+		}
+
+		Set<Slot> slots = pile.collect().castToSet();
+
+		assertThat("All Slots allocated are available", slots, iterableWithSize(threadCnt * backlog * 2));
+	}
+
+	@Test
+	public void testPartitionedReferencePileThroughput() throws InterruptedException {
+		int backlog = 64 * 1024;
+		long start = System.nanoTime();
+
+		PartitionedReferencePile<Slot> pile = new PartitionedReferencePile<Slot>(backlog, Slot::new);
+		CountDownLatch latch = new CountDownLatch(threadCnt);
+		for (int i = 0; i < threadCnt; i++) {
+			int idx = i;
+			pool.submit(() -> {
+				for (int j = 0; j < backlog; j++) {
+					Slot s = pile.get();
+					s.var0 = idx;
+					s.var1 = j;
+				}
+
+				for (Slot s : pile) {
+					int slotIdx = s.var0;
+				}
+				latch.countDown();
+			});
+		}
+		latch.await(5, TimeUnit.SECONDS);
+
+		long end = System.nanoTime();
+		double elapsed = TimeUnit.MILLISECONDS.convert(end - start, TimeUnit.NANOSECONDS);
+		System.out.println(String.format("throughput: %s ops/ms", (long) ((threadCnt * backlog) / elapsed)));
+	}
+
+	static class Slot {
+		volatile int var0;
+		volatile int var1;
+
+		@Override
+		public String toString() {
+			return "Slot{" +
+					"var0=" + var0 +
+					", var1=" + var1 +
+					'}';
+		}
+	}
+
+}
diff --git a/reactor-core/src/test/java/reactor/util/StringUtilsTests.java b/reactor-core/src/test/java/reactor/util/StringUtilsTests.java
new file mode 100644
index 0000000..3e8d10b
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/util/StringUtilsTests.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Rick Evans
+ */
+public class StringUtilsTests extends TestCase {
+
+	public void testHasTextBlank() throws Exception {
+		String blank = "          ";
+		assertEquals(false, StringUtils.hasText(blank));
+	}
+
+	public void testHasTextNullEmpty() throws Exception {
+		assertEquals(false, StringUtils.hasText(null));
+		assertEquals(false, StringUtils.hasText(""));
+	}
+
+	public void testHasTextValid() throws Exception {
+		assertEquals(true, StringUtils.hasText("t"));
+	}
+
+	public void testContainsWhitespace() throws Exception {
+		assertFalse(StringUtils.containsWhitespace(null));
+		assertFalse(StringUtils.containsWhitespace(""));
+		assertFalse(StringUtils.containsWhitespace("a"));
+		assertFalse(StringUtils.containsWhitespace("abc"));
+		assertTrue(StringUtils.containsWhitespace(" "));
+		assertTrue(StringUtils.containsWhitespace(" a"));
+		assertTrue(StringUtils.containsWhitespace("abc "));
+		assertTrue(StringUtils.containsWhitespace("a b"));
+		assertTrue(StringUtils.containsWhitespace("a  b"));
+	}
+
+	public void testTrimWhitespace() throws Exception {
+		assertEquals(null, StringUtils.trimWhitespace(null));
+		assertEquals("", StringUtils.trimWhitespace(""));
+		assertEquals("", StringUtils.trimWhitespace(" "));
+		assertEquals("", StringUtils.trimWhitespace("\t"));
+		assertEquals("a", StringUtils.trimWhitespace(" a"));
+		assertEquals("a", StringUtils.trimWhitespace("a "));
+		assertEquals("a", StringUtils.trimWhitespace(" a "));
+		assertEquals("a b", StringUtils.trimWhitespace(" a b "));
+		assertEquals("a b  c", StringUtils.trimWhitespace(" a b  c "));
+	}
+
+	public void testTrimAllWhitespace() throws Exception {
+		assertEquals("", StringUtils.trimAllWhitespace(""));
+		assertEquals("", StringUtils.trimAllWhitespace(" "));
+		assertEquals("", StringUtils.trimAllWhitespace("\t"));
+		assertEquals("a", StringUtils.trimAllWhitespace(" a"));
+		assertEquals("a", StringUtils.trimAllWhitespace("a "));
+		assertEquals("a", StringUtils.trimAllWhitespace(" a "));
+		assertEquals("ab", StringUtils.trimAllWhitespace(" a b "));
+		assertEquals("abc", StringUtils.trimAllWhitespace(" a b  c "));
+	}
+
+	public void testTrimLeadingWhitespace() throws Exception {
+		assertEquals(null, StringUtils.trimLeadingWhitespace(null));
+		assertEquals("", StringUtils.trimLeadingWhitespace(""));
+		assertEquals("", StringUtils.trimLeadingWhitespace(" "));
+		assertEquals("", StringUtils.trimLeadingWhitespace("\t"));
+		assertEquals("a", StringUtils.trimLeadingWhitespace(" a"));
+		assertEquals("a ", StringUtils.trimLeadingWhitespace("a "));
+		assertEquals("a ", StringUtils.trimLeadingWhitespace(" a "));
+		assertEquals("a b ", StringUtils.trimLeadingWhitespace(" a b "));
+		assertEquals("a b  c ", StringUtils.trimLeadingWhitespace(" a b  c "));
+	}
+
+	public void testTrimTrailingWhitespace() throws Exception {
+		assertEquals(null, StringUtils.trimTrailingWhitespace(null));
+		assertEquals("", StringUtils.trimTrailingWhitespace(""));
+		assertEquals("", StringUtils.trimTrailingWhitespace(" "));
+		assertEquals("", StringUtils.trimTrailingWhitespace("\t"));
+		assertEquals("a", StringUtils.trimTrailingWhitespace("a "));
+		assertEquals(" a", StringUtils.trimTrailingWhitespace(" a"));
+		assertEquals(" a", StringUtils.trimTrailingWhitespace(" a "));
+		assertEquals(" a b", StringUtils.trimTrailingWhitespace(" a b "));
+		assertEquals(" a b  c", StringUtils.trimTrailingWhitespace(" a b  c "));
+	}
+
+	public void testTrimLeadingCharacter() throws Exception {
+		assertEquals(null, StringUtils.trimLeadingCharacter(null, ' '));
+		assertEquals("", StringUtils.trimLeadingCharacter("", ' '));
+		assertEquals("", StringUtils.trimLeadingCharacter(" ", ' '));
+		assertEquals("\t", StringUtils.trimLeadingCharacter("\t", ' '));
+		assertEquals("a", StringUtils.trimLeadingCharacter(" a", ' '));
+		assertEquals("a ", StringUtils.trimLeadingCharacter("a ", ' '));
+		assertEquals("a ", StringUtils.trimLeadingCharacter(" a ", ' '));
+		assertEquals("a b ", StringUtils.trimLeadingCharacter(" a b ", ' '));
+		assertEquals("a b  c ", StringUtils.trimLeadingCharacter(" a b  c ", ' '));
+	}
+
+	public void testTrimTrailingCharacter() throws Exception {
+		assertEquals(null, StringUtils.trimTrailingCharacter(null, ' '));
+		assertEquals("", StringUtils.trimTrailingCharacter("", ' '));
+		assertEquals("", StringUtils.trimTrailingCharacter(" ", ' '));
+		assertEquals("\t", StringUtils.trimTrailingCharacter("\t", ' '));
+		assertEquals("a", StringUtils.trimTrailingCharacter("a ", ' '));
+		assertEquals(" a", StringUtils.trimTrailingCharacter(" a", ' '));
+		assertEquals(" a", StringUtils.trimTrailingCharacter(" a ", ' '));
+		assertEquals(" a b", StringUtils.trimTrailingCharacter(" a b ", ' '));
+		assertEquals(" a b  c", StringUtils.trimTrailingCharacter(" a b  c ", ' '));
+	}
+
+	public void testCountOccurrencesOf() {
+		assertTrue("nullx2 = 0",
+				StringUtils.countOccurrencesOf(null, null) == 0);
+		assertTrue("null string = 0",
+				StringUtils.countOccurrencesOf("s", null) == 0);
+		assertTrue("null substring = 0",
+				StringUtils.countOccurrencesOf(null, "s") == 0);
+		String s = "erowoiueoiur";
+		assertTrue("not found = 0",
+				StringUtils.countOccurrencesOf(s, "WERWER") == 0);
+		assertTrue("not found char = 0",
+				StringUtils.countOccurrencesOf(s, "x") == 0);
+		assertTrue("not found ws = 0",
+				StringUtils.countOccurrencesOf(s, " ") == 0);
+		assertTrue("not found empty string = 0",
+				StringUtils.countOccurrencesOf(s, "") == 0);
+		assertTrue("found char=2", StringUtils.countOccurrencesOf(s, "e") == 2);
+		assertTrue("found substring=2",
+				StringUtils.countOccurrencesOf(s, "oi") == 2);
+		assertTrue("found substring=2",
+				StringUtils.countOccurrencesOf(s, "oiu") == 2);
+		assertTrue("found substring=3",
+				StringUtils.countOccurrencesOf(s, "oiur") == 1);
+		assertTrue("test last", StringUtils.countOccurrencesOf(s, "r") == 2);
+	}
+
+	public void testReplace() throws Exception {
+		String inString = "a6AazAaa77abaa";
+		String oldPattern = "aa";
+		String newPattern = "foo";
+
+		// Simple replace
+		String s = StringUtils.replace(inString, oldPattern, newPattern);
+		assertTrue("Replace 1 worked", s.equals("a6AazAfoo77abfoo"));
+
+		// Non match: no change
+		s = StringUtils.replace(inString, "qwoeiruqopwieurpoqwieur", newPattern);
+		assertTrue("Replace non matched is equal", s.equals(inString));
+
+		// Null new pattern: should ignore
+		s = StringUtils.replace(inString, oldPattern, null);
+		assertTrue("Replace non matched is equal", s.equals(inString));
+
+		// Null old pattern: should ignore
+		s = StringUtils.replace(inString, null, newPattern);
+		assertTrue("Replace non matched is equal", s.equals(inString));
+	}
+
+	public void testDelete() throws Exception {
+		String inString = "The quick brown fox jumped over the lazy dog";
+
+		String noThe = StringUtils.delete(inString, "the");
+		assertTrue("Result has no the [" + noThe + "]",
+				noThe.equals("The quick brown fox jumped over  lazy dog"));
+
+		String nohe = StringUtils.delete(inString, "he");
+		assertTrue("Result has no he [" + nohe + "]",
+				nohe.equals("T quick brown fox jumped over t lazy dog"));
+
+		String nosp = StringUtils.delete(inString, " ");
+		assertTrue("Result has no spaces",
+				nosp.equals("Thequickbrownfoxjumpedoverthelazydog"));
+
+		String killEnd = StringUtils.delete(inString, "dog");
+		assertTrue("Result has no dog",
+				killEnd.equals("The quick brown fox jumped over the lazy "));
+
+		String mismatch = StringUtils.delete(inString, "dxxcxcxog");
+		assertTrue("Result is unchanged", mismatch.equals(inString));
+
+		String nochange = StringUtils.delete(inString, "");
+		assertTrue("Result is unchanged", nochange.equals(inString));
+	}
+
+	public void testDeleteAny() throws Exception {
+		String inString = "Able was I ere I saw Elba";
+
+		String res = StringUtils.deleteAny(inString, "I");
+		assertTrue("Result has no Is [" + res + "]", res.equals("Able was  ere  saw Elba"));
+
+		res = StringUtils.deleteAny(inString, "AeEba!");
+		assertTrue("Result has no Is [" + res + "]", res.equals("l ws I r I sw l"));
+
+		String mismatch = StringUtils.deleteAny(inString, "#@$#$^");
+		assertTrue("Result is unchanged", mismatch.equals(inString));
+
+		String whitespace = "This is\n\n\n    \t   a messagy string with whitespace\n";
+		assertTrue("Has CR", whitespace.indexOf("\n") != -1);
+		assertTrue("Has tab", whitespace.indexOf("\t") != -1);
+		assertTrue("Has  sp", whitespace.indexOf(" ") != -1);
+		String cleaned = StringUtils.deleteAny(whitespace, "\n\t ");
+		assertTrue("Has no CR", cleaned.indexOf("\n") == -1);
+		assertTrue("Has no tab", cleaned.indexOf("\t") == -1);
+		assertTrue("Has no sp", cleaned.indexOf(" ") == -1);
+		assertTrue("Still has chars", cleaned.length() > 10);
+	}
+
+
+	public void testQuote() {
+		assertEquals("'myString'", StringUtils.quote("myString"));
+		assertEquals("''", StringUtils.quote(""));
+		assertNull(StringUtils.quote(null));
+	}
+
+	public void testQuoteIfString() {
+		assertEquals("'myString'", StringUtils.quoteIfString("myString"));
+		assertEquals("''", StringUtils.quoteIfString(""));
+		assertEquals(new Integer(5), StringUtils.quoteIfString(new Integer(5)));
+		assertNull(StringUtils.quoteIfString(null));
+	}
+
+	public void testUnqualify() {
+		String qualified = "i.am.not.unqualified";
+		assertEquals("unqualified", StringUtils.unqualify(qualified));
+	}
+
+	public void testCapitalize() {
+		String capitalized = "i am not capitalized";
+		assertEquals("I am not capitalized", StringUtils.capitalize(capitalized));
+	}
+
+	public void testUncapitalize() {
+		String capitalized = "I am capitalized";
+		assertEquals("i am capitalized", StringUtils.uncapitalize(capitalized));
+	}
+
+	public void testGetFilename() {
+		assertEquals(null, StringUtils.getFilename(null));
+		assertEquals("", StringUtils.getFilename(""));
+		assertEquals("myfile", StringUtils.getFilename("myfile"));
+		assertEquals("myfile", StringUtils.getFilename("mypath/myfile"));
+		assertEquals("myfile.", StringUtils.getFilename("myfile."));
+		assertEquals("myfile.", StringUtils.getFilename("mypath/myfile."));
+		assertEquals("myfile.txt", StringUtils.getFilename("myfile.txt"));
+		assertEquals("myfile.txt", StringUtils.getFilename("mypath/myfile.txt"));
+	}
+
+	public void testGetFilenameExtension() {
+		assertEquals(null, StringUtils.getFilenameExtension(null));
+		assertEquals(null, StringUtils.getFilenameExtension(""));
+		assertEquals(null, StringUtils.getFilenameExtension("myfile"));
+		assertEquals(null, StringUtils.getFilenameExtension("myPath/myfile"));
+		assertEquals(null, StringUtils.getFilenameExtension("/home/user/.m2/settings/myfile"));
+		assertEquals("", StringUtils.getFilenameExtension("myfile."));
+		assertEquals("", StringUtils.getFilenameExtension("myPath/myfile."));
+		assertEquals("txt", StringUtils.getFilenameExtension("myfile.txt"));
+		assertEquals("txt", StringUtils.getFilenameExtension("mypath/myfile.txt"));
+		assertEquals("txt", StringUtils.getFilenameExtension("/home/user/.m2/settings/myfile.txt"));
+	}
+
+	public void testStripFilenameExtension() {
+		assertEquals(null, StringUtils.stripFilenameExtension(null));
+		assertEquals("", StringUtils.stripFilenameExtension(""));
+		assertEquals("myfile", StringUtils.stripFilenameExtension("myfile"));
+		assertEquals("myfile", StringUtils.stripFilenameExtension("myfile."));
+		assertEquals("myfile", StringUtils.stripFilenameExtension("myfile.txt"));
+		assertEquals("mypath/myfile", StringUtils.stripFilenameExtension("mypath/myfile"));
+		assertEquals("mypath/myfile", StringUtils.stripFilenameExtension("mypath/myfile."));
+		assertEquals("mypath/myfile", StringUtils.stripFilenameExtension("mypath/myfile.txt"));
+		assertEquals("/home/user/.m2/settings/myfile", StringUtils.stripFilenameExtension("/home/user/.m2/settings/myfile"));
+		assertEquals("/home/user/.m2/settings/myfile", StringUtils.stripFilenameExtension("/home/user/.m2/settings/myfile."));
+		assertEquals("/home/user/.m2/settings/myfile", StringUtils.stripFilenameExtension("/home/user/.m2/settings/myfile.txt"));
+	}
+
+	public void testCleanPath() {
+		assertEquals("mypath/myfile", StringUtils.cleanPath("mypath/myfile"));
+		assertEquals("mypath/myfile", StringUtils.cleanPath("mypath\\myfile"));
+		assertEquals("mypath/myfile", StringUtils.cleanPath("mypath/../mypath/myfile"));
+		assertEquals("mypath/myfile", StringUtils.cleanPath("mypath/myfile/../../mypath/myfile"));
+		assertEquals("../mypath/myfile", StringUtils.cleanPath("../mypath/myfile"));
+		assertEquals("../mypath/myfile", StringUtils.cleanPath("../mypath/../mypath/myfile"));
+		assertEquals("../mypath/myfile", StringUtils.cleanPath("mypath/../../mypath/myfile"));
+		assertEquals("/../mypath/myfile", StringUtils.cleanPath("/../mypath/myfile"));
+	}
+
+	public void testPathEquals() {
+		assertTrue("Must be true for the same strings",
+				StringUtils.pathEquals("/dummy1/dummy2/dummy3",
+						"/dummy1/dummy2/dummy3"));
+		assertTrue("Must be true for the same win strings",
+				StringUtils.pathEquals("C:\\dummy1\\dummy2\\dummy3",
+						"C:\\dummy1\\dummy2\\dummy3"));
+		assertTrue("Must be true for one top path on 1",
+				StringUtils.pathEquals("/dummy1/bin/../dummy2/dummy3",
+						"/dummy1/dummy2/dummy3"));
+		assertTrue("Must be true for one win top path on 2",
+				StringUtils.pathEquals("C:\\dummy1\\dummy2\\dummy3",
+						"C:\\dummy1\\bin\\..\\dummy2\\dummy3"));
+		assertTrue("Must be true for two top paths on 1",
+				StringUtils.pathEquals("/dummy1/bin/../dummy2/bin/../dummy3",
+						"/dummy1/dummy2/dummy3"));
+		assertTrue("Must be true for two win top paths on 2",
+				StringUtils.pathEquals("C:\\dummy1\\dummy2\\dummy3",
+						"C:\\dummy1\\bin\\..\\dummy2\\bin\\..\\dummy3"));
+		assertTrue("Must be true for double top paths on 1",
+				StringUtils.pathEquals("/dummy1/bin/tmp/../../dummy2/dummy3",
+						"/dummy1/dummy2/dummy3"));
+		assertTrue("Must be true for double top paths on 2 with similarity",
+				StringUtils.pathEquals("/dummy1/dummy2/dummy3",
+						"/dummy1/dum/dum/../../dummy2/dummy3"));
+		assertTrue("Must be true for current paths",
+				StringUtils.pathEquals("./dummy1/dummy2/dummy3",
+						"dummy1/dum/./dum/../../dummy2/dummy3"));
+		assertFalse("Must be false for relative/absolute paths",
+				StringUtils.pathEquals("./dummy1/dummy2/dummy3",
+						"/dummy1/dum/./dum/../../dummy2/dummy3"));
+		assertFalse("Must be false for different strings",
+				StringUtils.pathEquals("/dummy1/dummy2/dummy3",
+						"/dummy1/dummy4/dummy3"));
+		assertFalse("Must be false for one false path on 1",
+				StringUtils.pathEquals("/dummy1/bin/tmp/../dummy2/dummy3",
+						"/dummy1/dummy2/dummy3"));
+		assertFalse("Must be false for one false win top path on 2",
+				StringUtils.pathEquals("C:\\dummy1\\dummy2\\dummy3",
+						"C:\\dummy1\\bin\\tmp\\..\\dummy2\\dummy3"));
+		assertFalse("Must be false for top path on 1 + difference",
+				StringUtils.pathEquals("/dummy1/bin/../dummy2/dummy3",
+						"/dummy1/dummy2/dummy4"));
+	}
+
+	public void testConcatenateStringArrays() {
+		String[] input1 = new String[] {"myString2"};
+		String[] input2 = new String[] {"myString1", "myString2"};
+		String[] result = StringUtils.concatenateStringArrays(input1, input2);
+		assertEquals(3, result.length);
+		assertEquals("myString2", result[0]);
+		assertEquals("myString1", result[1]);
+		assertEquals("myString2", result[2]);
+
+		assertEquals(input1, StringUtils.concatenateStringArrays(input1, null));
+		assertEquals(input2, StringUtils.concatenateStringArrays(null, input2));
+		assertNull(StringUtils.concatenateStringArrays(null, null));
+	}
+
+	public void testMergeStringArrays() {
+		String[] input1 = new String[] {"myString2"};
+		String[] input2 = new String[] {"myString1", "myString2"};
+		String[] result = StringUtils.mergeStringArrays(input1, input2);
+		assertEquals(2, result.length);
+		assertEquals("myString2", result[0]);
+		assertEquals("myString1", result[1]);
+
+		assertEquals(input1, StringUtils.mergeStringArrays(input1, null));
+		assertEquals(input2, StringUtils.mergeStringArrays(null, input2));
+		assertNull(StringUtils.mergeStringArrays(null, null));
+	}
+
+	public void testSortStringArray() {
+		String[] input = new String[] {"myString2"};
+		input = StringUtils.addStringToArray(input, "myString1");
+		assertEquals("myString2", input[0]);
+		assertEquals("myString1", input[1]);
+
+		StringUtils.sortStringArray(input);
+		assertEquals("myString1", input[0]);
+		assertEquals("myString2", input[1]);
+	}
+
+	public void testRemoveDuplicateStrings() {
+		String[] input = new String[] {"myString2", "myString1", "myString2"};
+		input = StringUtils.removeDuplicateStrings(input);
+		assertEquals("myString1", input[0]);
+		assertEquals("myString2", input[1]);
+	}
+
+	public void testSplitArrayElementsIntoProperties() {
+		String[] input = new String[] {"key1=value1 ", "key2 =\"value2\""};
+		Properties result = StringUtils.splitArrayElementsIntoProperties(input, "=");
+		assertEquals("value1", result.getProperty("key1"));
+		assertEquals("\"value2\"", result.getProperty("key2"));
+	}
+
+	public void testSplitArrayElementsIntoPropertiesAndDeletedChars() {
+		String[] input = new String[] {"key1=value1 ", "key2 =\"value2\""};
+		Properties result = StringUtils.splitArrayElementsIntoProperties(input, "=", "\"");
+		assertEquals("value1", result.getProperty("key1"));
+		assertEquals("value2", result.getProperty("key2"));
+	}
+
+	public void testTokenizeToStringArray() {
+		String[] sa = StringUtils.tokenizeToStringArray("a,b , ,c", ",");
+		assertEquals(3, sa.length);
+		assertTrue("components are correct",
+				sa[0].equals("a") && sa[1].equals("b") && sa[2].equals("c"));
+	}
+
+	public void testTokenizeToStringArrayWithNotIgnoreEmptyTokens() {
+		String[] sa = StringUtils.tokenizeToStringArray("a,b , ,c", ",", true, false);
+		assertEquals(4, sa.length);
+		assertTrue("components are correct",
+				sa[0].equals("a") && sa[1].equals("b") && sa[2].equals("") && sa[3].equals("c"));
+	}
+
+	public void testTokenizeToStringArrayWithNotTrimTokens() {
+		String[] sa = StringUtils.tokenizeToStringArray("a,b ,c", ",", false, true);
+		assertEquals(3, sa.length);
+		assertTrue("components are correct",
+				sa[0].equals("a") && sa[1].equals("b ") && sa[2].equals("c"));
+	}
+
+	public void testCommaDelimitedListToStringArrayWithNullProducesEmptyArray() {
+		String[] sa = StringUtils.commaDelimitedListToStringArray(null);
+		assertTrue("String array isn't null with null input", sa != null);
+		assertTrue("String array length == 0 with null input", sa.length == 0);
+	}
+
+	public void testCommaDelimitedListToStringArrayWithEmptyStringProducesEmptyArray() {
+		String[] sa = StringUtils.commaDelimitedListToStringArray("");
+		assertTrue("String array isn't null with null input", sa != null);
+		assertTrue("String array length == 0 with null input", sa.length == 0);
+	}
+
+	private void testStringArrayReverseTransformationMatches(String[] sa) {
+		String[] reverse =
+				StringUtils.commaDelimitedListToStringArray(StringUtils.arrayToCommaDelimitedString(sa));
+		assertEquals("Reverse transformation is equal",
+				Arrays.asList(sa),
+				Arrays.asList(reverse));
+	}
+
+	public void testDelimitedListToStringArrayWithComma() {
+		String[] sa = StringUtils.delimitedListToStringArray("a,b", ",");
+		assertEquals(2, sa.length);
+		assertEquals("a", sa[0]);
+		assertEquals("b", sa[1]);
+	}
+
+	public void testDelimitedListToStringArrayWithSemicolon() {
+		String[] sa = StringUtils.delimitedListToStringArray("a;b", ";");
+		assertEquals(2, sa.length);
+		assertEquals("a", sa[0]);
+		assertEquals("b", sa[1]);
+	}
+
+	public void testDelimitedListToStringArrayWithEmptyString() {
+		String[] sa = StringUtils.delimitedListToStringArray("a,b", "");
+		assertEquals(3, sa.length);
+		assertEquals("a", sa[0]);
+		assertEquals(",", sa[1]);
+		assertEquals("b", sa[2]);
+	}
+
+	public void testDelimitedListToStringArrayWithNullDelimiter() {
+		String[] sa = StringUtils.delimitedListToStringArray("a,b", null);
+		assertEquals(1, sa.length);
+		assertEquals("a,b", sa[0]);
+	}
+
+	public void testCommaDelimitedListToStringArrayMatchWords() {
+		// Could read these from files
+		String[] sa = new String[] {"foo", "bar", "big"};
+		doTestCommaDelimitedListToStringArrayLegalMatch(sa);
+		testStringArrayReverseTransformationMatches(sa);
+
+		sa = new String[] {"a", "b", "c"};
+		doTestCommaDelimitedListToStringArrayLegalMatch(sa);
+		testStringArrayReverseTransformationMatches(sa);
+
+		// Test same words
+		sa = new String[] {"AA", "AA", "AA", "AA", "AA"};
+		doTestCommaDelimitedListToStringArrayLegalMatch(sa);
+		testStringArrayReverseTransformationMatches(sa);
+	}
+
+	public void testCommaDelimitedListToStringArraySingleString() {
+		// Could read these from files
+		String s = "woeirqupoiewuropqiewuorpqiwueopriquwopeiurqopwieur";
+		String[] sa = StringUtils.commaDelimitedListToStringArray(s);
+		assertTrue("Found one String with no delimiters", sa.length == 1);
+		assertTrue("Single array entry matches input String with no delimiters",
+				sa[0].equals(s));
+	}
+
+	public void testCommaDelimitedListToStringArrayWithOtherPunctuation() {
+		// Could read these from files
+		String[] sa = new String[] {"xcvwert4456346&*.", "///", ".!", ".", ";"};
+		doTestCommaDelimitedListToStringArrayLegalMatch(sa);
+	}
+
+	/**
+	 * We expect to see the empty Strings in the output.
+	 */
+	public void testCommaDelimitedListToStringArrayEmptyStrings() {
+		// Could read these from files
+		String[] sa = StringUtils.commaDelimitedListToStringArray("a,,b");
+		assertEquals("a,,b produces array length 3", 3, sa.length);
+		assertTrue("components are correct",
+				sa[0].equals("a") && sa[1].equals("") && sa[2].equals("b"));
+
+		sa = new String[] {"", "", "a", ""};
+		doTestCommaDelimitedListToStringArrayLegalMatch(sa);
+	}
+
+	private void doTestCommaDelimitedListToStringArrayLegalMatch(String[] components) {
+		StringBuffer sbuf = new StringBuffer();
+		for (int i = 0; i < components.length; i++) {
+			if (i != 0) {
+				sbuf.append(",");
+			}
+			sbuf.append(components[i]);
+		}
+		String[] sa = StringUtils.commaDelimitedListToStringArray(sbuf.toString());
+		assertTrue("String array isn't null with legal match", sa != null);
+		assertEquals("String array length is correct with legal match", components.length, sa.length);
+		assertTrue("Output equals input", Arrays.equals(sa, components));
+	}
+
+	public void testEndsWithIgnoreCase() {
+		String suffix = "fOo";
+		assertTrue(StringUtils.endsWithIgnoreCase("foo", suffix));
+		assertTrue(StringUtils.endsWithIgnoreCase("Foo", suffix));
+		assertTrue(StringUtils.endsWithIgnoreCase("barfoo", suffix));
+		assertTrue(StringUtils.endsWithIgnoreCase("barbarfoo", suffix));
+		assertTrue(StringUtils.endsWithIgnoreCase("barFoo", suffix));
+		assertTrue(StringUtils.endsWithIgnoreCase("barBarFoo", suffix));
+		assertTrue(StringUtils.endsWithIgnoreCase("barfoO", suffix));
+		assertTrue(StringUtils.endsWithIgnoreCase("barFOO", suffix));
+		assertTrue(StringUtils.endsWithIgnoreCase("barfOo", suffix));
+		assertFalse(StringUtils.endsWithIgnoreCase(null, suffix));
+		assertFalse(StringUtils.endsWithIgnoreCase("barfOo", null));
+		assertFalse(StringUtils.endsWithIgnoreCase("b", suffix));
+	}
+
+	public void testParseLocaleStringSunnyDay() throws Exception {
+		Locale expectedLocale = Locale.UK;
+		Locale locale = StringUtils.parseLocaleString(expectedLocale.toString());
+		assertNotNull("When given a bona-fide Locale string, must not return null.", locale);
+		assertEquals(expectedLocale, locale);
+	}
+
+	public void testParseLocaleStringWithMalformedLocaleString() throws Exception {
+		Locale locale = StringUtils.parseLocaleString("_banjo_on_my_knee");
+		assertNotNull("When given a malformed Locale string, must not return null.", locale);
+	}
+
+	public void testParseLocaleStringWithEmptyLocaleStringYieldsNullLocale() throws Exception {
+		Locale locale = StringUtils.parseLocaleString("");
+		assertNull("When given an empty Locale string, must return null.", locale);
+	}
+
+	/**
+	 * <a href="http://opensource.atlassian.com/projects/spring/browse/SPR-8637">See SPR-8637</a>.
+	 */
+	public void testParseLocaleWithMultiSpecialCharactersInVariant() throws Exception {
+		final String variant = "proper-northern";
+		final String localeString = "en_GB_" + variant;
+		Locale locale = StringUtils.parseLocaleString(localeString);
+		assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
+	}
+
+	/**
+	 * <a href="http://opensource.atlassian.com/projects/spring/browse/SPR-3671">See SPR-3671</a>.
+	 */
+	public void testParseLocaleWithMultiValuedVariant() throws Exception {
+		final String variant = "proper_northern";
+		final String localeString = "en_GB_" + variant;
+		Locale locale = StringUtils.parseLocaleString(localeString);
+		assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
+	}
+
+	/**
+	 * <a href="http://opensource.atlassian.com/projects/spring/browse/SPR-3671">See SPR-3671</a>.
+	 */
+	public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparators() throws Exception {
+		final String variant = "proper northern";
+		final String localeString = "en GB " + variant;
+		Locale locale = StringUtils.parseLocaleString(localeString);
+		assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
+	}
+
+	/**
+	 * <a href="http://opensource.atlassian.com/projects/spring/browse/SPR-3671">See SPR-3671</a>.
+	 */
+	public void testParseLocaleWithMultiValuedVariantUsingMixtureOfUnderscoresAndSpacesAsSeparators() throws Exception {
+		final String variant = "proper northern";
+		final String localeString = "en_GB_" + variant;
+		Locale locale = StringUtils.parseLocaleString(localeString);
+		assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
+	}
+
+	/**
+	 * <a href="http://opensource.atlassian.com/projects/spring/browse/SPR-3671">See SPR-3671</a>.
+	 */
+	public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception {
+		final String variant = "proper northern";
+		final String localeString = "en GB            " + variant; // lots of whitespace
+		Locale locale = StringUtils.parseLocaleString(localeString);
+		assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
+	}
+
+	/**
+	 * <a href="http://opensource.atlassian.com/projects/spring/browse/SPR-3671">See SPR-3671</a>.
+	 */
+	public void testParseLocaleWithMultiValuedVariantUsingUnderscoresAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception {
+		final String variant = "proper_northern";
+		final String localeString = "en_GB_____" + variant; // lots of underscores
+		Locale locale = StringUtils.parseLocaleString(localeString);
+		assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
+	}
+
+	/**
+	 * <a href="http://opensource.atlassian.com/projects/spring/browse/SPR-7779">See SPR-7779</a>.
+	 */
+	public void testParseLocaleWithInvalidCharacters() {
+		try {
+			StringUtils.parseLocaleString("%0D%0AContent-length:30%0D%0A%0D%0A%3Cscript%3Ealert%28123%29%3C/script%3E");
+			fail("Should have thrown IllegalArgumentException");
+		}
+		catch (IllegalArgumentException ex) {
+			// expected
+		}
+	}
+
+	/**
+	 * See SPR-9420.
+	 */
+	public void testParseLocaleWithSameLowercaseTokenForLanguageAndCountry() {
+		assertEquals("tr_TR", StringUtils.parseLocaleString("tr_tr").toString());
+		assertEquals("bg_BG_vnt", StringUtils.parseLocaleString("bg_bg_vnt").toString());
+	}
+}
\ No newline at end of file
diff --git a/reactor-core/src/test/java/reactor/util/UUIDUtilsTests.java b/reactor-core/src/test/java/reactor/util/UUIDUtilsTests.java
new file mode 100644
index 0000000..abac04b
--- /dev/null
+++ b/reactor-core/src/test/java/reactor/util/UUIDUtilsTests.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.util;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.function.Supplier;
+
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @author Andy Wilkinson
+ */
+ at Ignore
+public class UUIDUtilsTests {
+
+	private static final long          clockNodeAndSeq    = new Random().nextLong();
+	private static final ReentrantLock lock               = new ReentrantLock();
+	private static final AtomicLong    lastTimeAtomicLong = new AtomicLong();
+	private static       long          lastTimeLong       = 0;
+
+	private static final Object MONITOR               = new Object();
+	private static final int[]  THREADS               = new int[]{1, 2, 4, 8, 16, 32};
+	private static final int[]  ITERATIONS_PER_THREAD = new int[]{1000000, 500000, 250000, 125000, 62500, 31250};
+	private static final int    TEST_ITERATIONS       = 10;
+
+	@Test
+	public void reentrantLockWithAtomicLongPerformance() throws InterruptedException {
+		doTest("ReentrantLock with AtomicLong", new Supplier<UUID>() {
+
+			@Override
+			public UUID get() {
+				long timeMillis = (System.currentTimeMillis() * 10000) + 0x01B21DD213814000L;
+
+				lock.lock();
+				try {
+					if(lastTimeAtomicLong.get() == timeMillis) {
+						timeMillis = lastTimeAtomicLong.incrementAndGet();
+					} else {
+						lastTimeAtomicLong.set(timeMillis);
+					}
+				} finally {
+					lock.unlock();
+				}
+
+				// time low
+				long time = timeMillis << 32;
+
+				// time mid
+				time |= (timeMillis & 0xFFFF00000000L) >> 16;
+
+				// time hi and version
+				time |= 0x1000 | ((timeMillis >> 48) & 0x0FFF); // version 1
+
+				return new UUID(time, clockNodeAndSeq);
+			}
+		});
+	}
+
+	@Test
+	public void synchronizedWithAtomicLongPerformance() throws InterruptedException {
+		doTest("Synchronized with AtomicLong", new Supplier<UUID>() {
+
+			@Override
+			public UUID get() {
+				long timeMillis = (System.currentTimeMillis() * 10000) + 0x01B21DD213814000L;
+
+				synchronized(MONITOR) {
+					if(lastTimeAtomicLong.get() == timeMillis) {
+						timeMillis = lastTimeAtomicLong.incrementAndGet();
+					} else {
+						lastTimeAtomicLong.set(timeMillis);
+					}
+				}
+
+				// time low
+				long time = timeMillis << 32;
+
+				// time mid
+				time |= (timeMillis & 0xFFFF00000000L) >> 16;
+
+				// time hi and version
+				time |= 0x1000 | ((timeMillis >> 48) & 0x0FFF); // version 1
+
+				return new UUID(time, clockNodeAndSeq);
+			}
+
+		});
+	}
+
+	@Test
+	public void reentrantLockWithLongPerformance() throws InterruptedException {
+		doTest("ReentrantLock with long", new Supplier<UUID>() {
+
+			@Override
+			public UUID get() {
+				long timeMillis = (System.currentTimeMillis() * 10000) + 0x01B21DD213814000L;
+
+				lock.lock();
+				try {
+					if(lastTimeLong == timeMillis) {
+						timeMillis = ++lastTimeLong;
+					} else {
+						lastTimeLong = timeMillis;
+					}
+				} finally {
+					lock.unlock();
+				}
+
+				// time low
+				long time = timeMillis << 32;
+
+				// time mid
+				time |= (timeMillis & 0xFFFF00000000L) >> 16;
+
+				// time hi and version
+				time |= 0x1000 | ((timeMillis >> 48) & 0x0FFF); // version 1
+
+				return new UUID(time, clockNodeAndSeq);
+			}
+		});
+	}
+
+	@Test
+	public void synchronizedWithLongPerformance() throws InterruptedException {
+		doTest("Synchronized with long", new Supplier<UUID>() {
+
+			@Override
+			public UUID get() {
+				long timeMillis = (System.currentTimeMillis() * 10000) + 0x01B21DD213814000L;
+
+				synchronized(MONITOR) {
+					if(lastTimeLong == timeMillis) {
+						timeMillis = ++lastTimeLong;
+					} else {
+						lastTimeLong = timeMillis;
+					}
+				}
+
+				// time low
+				long time = timeMillis << 32;
+
+				// time mid
+				time |= (timeMillis & 0xFFFF00000000L) >> 16;
+
+				// time hi and version
+				time |= 0x1000 | ((timeMillis >> 48) & 0x0FFF); // version 1
+
+				return new UUID(time, clockNodeAndSeq);
+			}
+
+		});
+	}
+
+	@Test
+	public void timeBasedVersusRandom() throws InterruptedException {
+		doTest("Create time-based", new Supplier<UUID>() {
+			@Override
+			public UUID get() {
+				return UUIDUtils.random();
+			}
+		});
+		doTest("Create random", new Supplier<UUID>() {
+			@Override
+			public UUID get() {
+				return UUIDUtils.random();
+			}
+		});
+	}
+
+	private void doTest(String description, final Supplier<UUID> uuidSupplier) throws InterruptedException {
+		for(int t = 0; t < THREADS.length; t++) {
+			int threads = THREADS[t];
+			final int iterations = ITERATIONS_PER_THREAD[t];
+			long[] durations = new long[TEST_ITERATIONS];
+
+			for(int i = 0; i < TEST_ITERATIONS; i++) {
+				final CountDownLatch latch = new CountDownLatch(threads);
+
+				long startTime = System.currentTimeMillis();
+
+				for(int j = 0; j < threads; j++) {
+					new Thread(new Runnable() {
+
+						@Override
+						public void run() {
+							for(int u = 0; u < iterations; u++) {
+								uuidSupplier.get();
+							}
+							latch.countDown();
+						}
+
+					}).start();
+				}
+
+				latch.await();
+
+				durations[i] = System.currentTimeMillis() - startTime;
+
+			}
+			System.out.println(description + " with " + threads + " thread" + (threads > 1 ? "s" : "") + ":");
+			System.out.println("\t average: " + getAverage(durations) + "ms");
+			System.out.println("\t     min: " + getMin(durations) + "ms");
+			System.out.println("\t     max: " + getMax(durations) + "ms");
+		}
+	}
+
+	private long getMin(long[] durations) {
+		long min = Long.MAX_VALUE;
+		for(long duration : durations) {
+			min = Math.min(duration, min);
+		}
+		return min;
+	}
+
+	private long getMax(long[] durations) {
+		long max = Long.MIN_VALUE;
+		for(long duration : durations) {
+			max = Math.max(duration, max);
+		}
+		return max;
+	}
+
+	private long getAverage(long[] durations) {
+		long total = 0;
+		for(long duration : durations) {
+			total += duration;
+		}
+		return total / durations.length;
+	}
+
+}
diff --git a/reactor-core/src/test/resources/META-INF/reactor/custom.properties b/reactor-core/src/test/resources/META-INF/reactor/custom.properties
new file mode 100644
index 0000000..ebca667
--- /dev/null
+++ b/reactor-core/src/test/resources/META-INF/reactor/custom.properties
@@ -0,0 +1,18 @@
+#
+# Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+reactor.dispatchers.alpha.type=synchronous
+reactor.dispatchers.default=alpha
\ No newline at end of file
diff --git a/reactor-core/src/test/resources/META-INF/reactor/override-custom.properties b/reactor-core/src/test/resources/META-INF/reactor/override-custom.properties
new file mode 100644
index 0000000..26dcb7d
--- /dev/null
+++ b/reactor-core/src/test/resources/META-INF/reactor/override-custom.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+reactor.dispatchers.alpha.type=ringBuffer
\ No newline at end of file
diff --git a/reactor-core/src/test/resources/META-INF/reactor/override-default.properties b/reactor-core/src/test/resources/META-INF/reactor/override-default.properties
new file mode 100644
index 0000000..d225c10
--- /dev/null
+++ b/reactor-core/src/test/resources/META-INF/reactor/override-default.properties
@@ -0,0 +1,17 @@
+#
+# Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+reactor.dispatchers.ringBuffer.backlog=512
\ No newline at end of file
diff --git a/reactor-core/src/test/resources/META-INF/reactor/unrecognized-type.properties b/reactor-core/src/test/resources/META-INF/reactor/unrecognized-type.properties
new file mode 100644
index 0000000..71ef1e6
--- /dev/null
+++ b/reactor-core/src/test/resources/META-INF/reactor/unrecognized-type.properties
@@ -0,0 +1,18 @@
+#
+# Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+reactor.dispatchers.alpha.type=unrecognized
+reactor.dispatchers.default=alpha
\ No newline at end of file
diff --git a/reactor-core/src/test/resources/logback.xml b/reactor-core/src/test/resources/logback.xml
new file mode 100644
index 0000000..5741cfb
--- /dev/null
+++ b/reactor-core/src/test/resources/logback.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~       http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration>
+
+	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>
+				%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+			</pattern>
+		</encoder>
+	</appender>
+
+	<logger name="reactor" level="debug"/>
+
+	<root level="info">
+		<appender-ref ref="stdout"/>
+	</root>
+
+</configuration>
\ No newline at end of file
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ComposableExtensions.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ComposableExtensions.groovy
new file mode 100644
index 0000000..d852e56
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ComposableExtensions.groovy
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.groovy.ext
+
+import groovy.transform.CompileStatic
+import groovy.transform.TypeCheckingMode
+import reactor.core.composable.Composable
+import reactor.core.composable.Deferred
+import reactor.core.composable.Promise
+import reactor.core.composable.Stream
+import reactor.function.*
+import reactor.groovy.support.*
+import reactor.tuple.Tuple2
+
+/**
+ * Glue for Groovy closures and operator overloading applied to Stream, Composable,
+ * Promise and Deferred.
+ * Also gives convenient Deferred handling by providing map/filter/consume operations
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ComposableExtensions {
+
+	/**
+	 * Alias
+	 */
+	static <T, X extends Composable<T>> X to(final X selfType, final Object key,
+	                                         final reactor.core.Observable observable) {
+		selfType.consume key, observable
+	}
+
+	/**
+	 * Closure converters
+	 */
+	static <T, V> Composable<V> map(final Deferred selfType, final Closure<V> closure) {
+		selfType.compose().map new ClosureFunction<T, V>(closure)
+	}
+
+	static <T, V> Stream<V> map(final Stream<T> selfType, final Closure<V> closure) {
+		selfType.map new ClosureFunction<T, V>(closure)
+	}
+
+	static <T, V> Promise<V> map(final Promise<T> selfType, final Closure<V> closure) {
+		selfType.map new ClosureFunction<T, V>(closure)
+	}
+
+	static <T> Stream<T> consume(final Stream<T> selfType, final Closure closure) {
+		selfType.consume new ClosureConsumer<T>(closure)
+	}
+
+	static <T, V, C extends Composable<V>> Stream<V> mapMany(final Stream<T> selfType, final Closure<C> closure) {
+		selfType.mapMany new ClosureFunction<T, C>(closure)
+	}
+
+	static <T, V, C extends Promise<V>> Promise<V> bind(final Promise<T> selfType, final Closure<C> closure) {
+		selfType.mapMany new ClosureFunction<T, C>(closure)
+	}
+
+	static <T> Composable<T> consume(final Deferred selfType, final Closure closure) {
+		selfType.compose().consume new ClosureConsumer<T>(closure)
+	}
+
+	static <T> Promise<T> consume(final Promise<T> selfType, final Closure closure) {
+		selfType.consume new ClosureConsumer<T>(closure)
+	}
+
+	static <T> Stream<T> filter(final Stream<T> selfType, final Closure<Boolean> closure) {
+		selfType.filter new ClosurePredicate<T>(closure)
+	}
+
+	static <T> Composable<T> filter(final Deferred selfType, final Closure<Boolean> closure) {
+		selfType.compose().filter new ClosurePredicate<T>(closure)
+	}
+
+	static <T> Promise<T> filter(final Promise<T> selfType, final Closure<Boolean> closure) {
+		selfType.filter new ClosurePredicate<T>(closure)
+	}
+
+	static <T, E> Stream<T> when(final Stream<T> selfType, final Class<E> exceptionType, final Closure closure) {
+		selfType.when exceptionType, new ClosureConsumer<E>(closure)
+	}
+
+	static <T, E> Composable<T> when(final Deferred selfType, final Class<E> exceptionType, final Closure closure) {
+		selfType.compose().when exceptionType, new ClosureConsumer<E>(closure)
+	}
+
+	static <T, E> Promise<T> when(final Promise<T> selfType, final Class<E> exceptionType, final Closure closure) {
+		selfType.when exceptionType, new ClosureConsumer<E>(closure)
+	}
+
+	@CompileStatic(TypeCheckingMode.SKIP)
+	static <T, V> Stream<V> reduce(final Stream<T> selfType, final Closure<V> closure, V initial = null) {
+		reduce selfType, closure, initial ? Functions.<V>supplier((V)initial) : (Supplier<V>)null
+	}
+
+	static <T, V> Stream<V> reduce(final Stream<T> selfType, final Closure<V> closure, Closure<V> initial) {
+		reduce selfType, closure, new ClosureSupplier(initial)
+	}
+
+	@CompileStatic(TypeCheckingMode.SKIP)
+	static <T, V> Stream<V> reduce(final Stream<T> selfType, final Closure<V> closure, Supplier<V> initial) {
+		selfType.reduce new ClosureReduce<T, V>(closure), initial
+	}
+
+	static <T> Promise<T> onError(final Promise<T> selfType, final Closure closure) {
+		selfType.onError new ClosureConsumer<Throwable>(closure)
+	}
+
+	static <T> Promise<T> onComplete(final Promise<T> selfType, final Closure closure) {
+		selfType.onComplete new ClosureConsumer<Promise<T>>(closure)
+	}
+
+	static <T> Promise<T> onSuccess(final Promise<T> selfType, final Closure closure) {
+		selfType.onSuccess new ClosureConsumer<T>(closure)
+	}
+
+	static <T, V> Promise<V> then(final Promise<T> selfType, final Closure<V> closureSuccess,
+	                              final Closure closureError = null) {
+		selfType.then(new ClosureFunction<T, V>(closureSuccess), closureError ?
+				new ClosureConsumer<Throwable>(closureError) : null)
+	}
+
+	/**
+	 * Operator overloading
+	 */
+
+	@CompileStatic(TypeCheckingMode.SKIP)
+	static <T, V> Stream<V> mod(final Stream<T> selfType, final Function<Tuple2<T, V>, V> other) {
+		selfType.reduce other, (Supplier<V>)null
+	}
+
+	static <T, V> Stream<V> mod(final Stream<T> selfType, final Closure<V> other) {
+		reduce selfType, other
+	}
+
+	//Mapping
+	static <T, V> Stream<V> or(final Stream<T> selfType, final Function<T, V> other) {
+		selfType.map other
+	}
+
+	static <T, V> Stream<V> or(final Stream<T> selfType, final Closure<V> other) {
+		map selfType, other
+	}
+
+	static <T, V> Composable<V> or(final Deferred selfType, final Function<T, V> other) {
+		selfType.compose().map other
+	}
+
+	static <T, V> Composable<V> or(final Deferred selfType, final Closure<V> other) {
+		map selfType, other
+	}
+
+	static <T, V> Promise<V> or(final Promise<T> selfType, final Function<T, V> other) {
+		selfType.then other, (Consumer<Throwable>) null
+	}
+
+	static <T, V> Promise<V> or(final Promise<T> selfType, final Closure<V> other) {
+		then selfType, other
+	}
+
+
+	//Filtering
+	static <T> Stream<T> and(final Stream<T> selfType, final Closure<Boolean> other) {
+		filter selfType, other
+	}
+
+	static <T> Promise<T> and(final Promise<T> selfType, final Closure<Boolean> other) {
+		filter selfType, other
+	}
+
+	static <T> Composable<T> and(final Deferred selfType, final Closure<Boolean> other) {
+		filter selfType, other
+	}
+
+	static <T> Stream<T> and(final Stream<T> selfType, final Predicate<T> other) {
+		selfType.filter other
+	}
+
+	static <T> Promise<T> and(final Promise<T> selfType, final Predicate<T> other) {
+		selfType.filter other
+	}
+
+	static <T> Composable<T> and(final Deferred selfType, final Predicate<T> other) {
+		selfType.compose().filter other
+	}
+
+
+	//Consuming
+	static <T> Stream<T> leftShift(final Stream<T> selfType, final Consumer<T> other) {
+		selfType.consume other
+	}
+
+	static <T> Composable<T> leftShift(final Deferred selfType, final Consumer<T> other) {
+		selfType.compose().consume other
+	}
+
+	static <T> Stream<T> leftShift(final Stream<T> selfType, final Closure other) {
+		consume selfType, other
+	}
+
+	static <T> Composable<T> leftShift(final Deferred selfType, final Closure other) {
+		consume selfType, other
+	}
+
+	static <T> Promise<T> leftShift(final Promise<T> selfType, final Consumer<T> other) {
+		selfType.onSuccess other
+	}
+
+	static <T> Promise<T> leftShift(final Promise<T> selfType, final Closure other) {
+		onSuccess selfType, other
+	}
+
+	//Accept
+	static <T> Consumer<T> leftShift(final Consumer<T> selfType, T value) {
+		selfType.accept value
+		selfType
+	}
+
+	static <T, X extends Composable<T>> Deferred<T, X> leftShift(final Deferred<T, X> selfType, T value) {
+		selfType.accept value
+		selfType
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ObservableExtensions.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ObservableExtensions.groovy
new file mode 100644
index 0000000..c800a60
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ObservableExtensions.groovy
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.groovy.ext
+
+import groovy.transform.CompileStatic
+import reactor.core.Reactor
+import reactor.event.Event
+import reactor.event.selector.Selectors
+import reactor.function.*
+import reactor.event.registry.Registration
+import reactor.event.selector.Selector
+import reactor.groovy.support.ClosureEventConsumer
+import reactor.groovy.support.ClosureEventFunction
+
+import static reactor.event.selector.Selectors.$
+import static reactor.event.selector.Selectors.object
+/**
+ * Extensions for providing syntax sugar for working with {@link reactor.core.Observable}s.
+ *
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ */
+ at CompileStatic
+class ObservableExtensions {
+	static final String ARG_DATA = 'data'
+	static final String ARG_TOPIC = 'for'
+
+	/**
+	 * Closure converters
+	 */
+	static <T, E extends Event<T>, V> Registration<Consumer<E>> receive(final reactor.core.Observable selfType,
+	                                                                    final Selector key,
+	                                                                    final Closure<V> closure) {
+		selfType.receive key, new ClosureEventFunction<E, V>(closure)
+	}
+
+	static Registration<Consumer> on(reactor.core.Observable selfType,
+	                                 Selector selector,
+	                                 @DelegatesTo(value = ClosureEventConsumer.ReplyDecorator, strategy = Closure.DELEGATE_FIRST)
+	                                 Closure handler) {
+		selfType.on selector, new ClosureEventConsumer(handler)
+	}
+
+	static Registration<Consumer> on(reactor.core.Observable selfType,
+	                                 String selector,
+	                                 @DelegatesTo(value = ClosureEventConsumer.ReplyDecorator, strategy = Closure.DELEGATE_FIRST) Closure handler) {
+		selfType.on object(selector), new ClosureEventConsumer(handler)
+	}
+
+	/**
+	 * Alias and Misc. Helpers
+	 */
+	static <T> Reactor send(Reactor selfType,
+	                        Object key,
+	                        T obj) {
+		selfType.send key, Event.<T> wrap(obj)
+	}
+
+	static <T> Reactor send(Reactor selfType,
+	                        Object key,
+	                        T obj,
+	                        @DelegatesTo(value = ClosureEventConsumer.ReplyDecorator, strategy = Closure.DELEGATE_FIRST) Closure handler)
+	{
+		send selfType, key, Event.wrap(obj), handler
+	}
+
+	static <T> Reactor send(Reactor selfType,
+	                        Object key,
+	                        Event<T> obj,
+	                        @DelegatesTo(value = ClosureEventConsumer.ReplyDecorator, strategy = Closure.DELEGATE_FIRST) Closure handler)
+	{
+		def replyTo = obj.replyTo ? $(obj.replyTo) : Selectors.anonymous()
+		selfType.on replyTo, new ClosureEventConsumer(handler)
+		selfType.send key, obj.setReplyTo(replyTo.object)
+	}
+
+	static <T> reactor.core.Observable notify(reactor.core.Observable selfType,
+	                                          Object key,
+	                                          T obj) {
+		selfType.notify key, Event.<T> wrap(obj)
+	}
+
+	static <T> reactor.core.Observable notify(reactor.core.Observable selfType,
+	                                          Object key,
+	                                          Supplier<Event<T>> obj) {
+		selfType.notify key, obj.get()
+	}
+
+	static reactor.core.Observable notify(reactor.core.Observable selfType,
+	                                      Object key) {
+		selfType.notify key, Event.<Void> wrap(null)
+	}
+
+	static <T> reactor.core.Observable notify(reactor.core.Observable selfType,
+	                                          String key,
+	                                          Closure<T> closure) {
+		selfType.notify key, Event.wrap((T) closure.call())
+	}
+
+	static reactor.core.Observable notify(final reactor.core.Observable selfType, final Map params) {
+		Object topic = params.remove ARG_TOPIC
+
+		def toSend
+		if (params) {
+			toSend = new Event(new Event.Headers(), params.remove(ARG_DATA))
+			for (entry in params.entrySet()) {
+				toSend.headers.set entry.key?.toString(), entry.value?.toString()
+			}
+		} else {
+			toSend = new Event(params.remove(ARG_DATA))
+		}
+
+		selfType.notify topic, toSend
+		selfType
+	}
+
+
+	/**
+	 * Alias
+	 */
+
+	static <T, V> void call(final Function<T, V> selfType, final T value) {
+		selfType.apply value
+	}
+
+	static <T> void call(final Consumer<T> selfType, final T value) {
+		selfType.accept value
+	}
+
+	static <T> void call(final Supplier<T> selfType) {
+		selfType.get()
+	}
+
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ProcessorExtensions.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ProcessorExtensions.groovy
new file mode 100644
index 0000000..5ef943c
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ProcessorExtensions.groovy
@@ -0,0 +1,49 @@
+package reactor.groovy.ext
+
+import groovy.transform.CompileStatic
+import reactor.core.processor.spec.ProcessorSpec
+import reactor.groovy.support.ClosureConsumer
+import reactor.groovy.support.ClosureSupplier
+
+/**
+ * Groovy extension module for {@link ProcessorSpec}.
+ *
+ * @author Jon Brisbin
+ */
+ at CompileStatic
+class ProcessorExtensions {
+
+  /**
+   * Provide a {@link Closure} as a {@code dataSupplier}.
+   *
+   * @param selfType
+   * @param closure
+   * @return
+   */
+  static <T> ProcessorSpec<T> dataSupplier(ProcessorSpec<T> selfType, Closure<T> closure) {
+    selfType.dataSupplier(new ClosureSupplier<T>(closure))
+  }
+
+  /**
+   * Provide a {@link Closure} as a {@link reactor.function.Consumer}.
+   *
+   * @param selfType
+   * @param closure
+   * @return
+   */
+  static <T> ProcessorSpec<T> consume(ProcessorSpec<T> selfType, Closure closure) {
+    selfType.consume(new ClosureConsumer<T>(closure))
+  }
+
+  /**
+   * Provide a {@link Closure} as an error {@link reactor.function.Consumer}.
+   *
+   * @param selfType
+   * @param closure
+   * @return
+   */
+  static <T> ProcessorSpec<T> when(ProcessorSpec<T> selfType, Class<Throwable> type, Closure closure) {
+    selfType.when(type, new ClosureConsumer<Throwable>(closure))
+  }
+
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ReactorStaticExtensions.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ReactorStaticExtensions.groovy
new file mode 100644
index 0000000..07a632a
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/ext/ReactorStaticExtensions.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy.ext
+
+import groovy.transform.CompileStatic
+import reactor.core.Reactor
+import reactor.core.composable.Promise
+import reactor.core.composable.spec.PromiseSpec;
+import reactor.core.composable.spec.Promises
+import reactor.core.spec.Reactors;
+import reactor.function.Functions;
+import reactor.core.Observable
+import reactor.groovy.support.ClosureConsumer
+import reactor.groovy.support.ClosureSupplier
+
+/**
+ * Static extensions for reactor-core classes, main purpose is to bind closure when required
+ *
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ReactorStaticExtensions {
+
+	/**
+	 * Closure converters
+	 */
+	static <T> void schedule(final Functions selfType, final T value, final Reactor reactor, final Closure closure) {
+		reactor.schedule new ClosureConsumer(closure), value
+	}
+
+	static <T> PromiseSpec<T> task(final Promises selfType, Closure<T> callback) {
+		Promises.task new ClosureSupplier<T>(callback)
+	}
+
+	static <T> PromiseSpec<T> from(final Promise<T> selfType, Closure<T> callback) {
+		task null, callback
+	}
+
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureConsumer.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureConsumer.groovy
new file mode 100644
index 0000000..bfd7d60
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureConsumer.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy.support
+
+import groovy.transform.CompileStatic
+import reactor.function.Consumer
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ClosureConsumer<T> implements Consumer<T> {
+
+	final Closure callback
+
+	ClosureConsumer(Closure cl) {
+		callback = cl
+	}
+
+	@Override
+	void accept(T arg) {
+		callback arg
+	}
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureEventConsumer.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureEventConsumer.groovy
new file mode 100644
index 0000000..ac2d130
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureEventConsumer.groovy
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy.support
+
+import groovy.transform.CompileStatic
+import reactor.core.Reactor
+import reactor.function.Consumer
+import reactor.event.Event
+import reactor.function.support.CancelConsumerException
+
+/**
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ClosureEventConsumer<T> implements Consumer<Event<T>> {
+
+	final Closure callback
+	final boolean eventArg
+
+	ClosureEventConsumer(Closure cl) {
+		callback = cl
+		callback.delegate = this
+		def argTypes = callback.parameterTypes
+		this.eventArg = Event.isAssignableFrom(argTypes[0])
+	}
+
+	void cancel() {
+		throw new CancelConsumerException()
+	}
+
+	@Override
+	void accept(Event<T> arg) {
+		def callback = this.callback
+		if (Reactor.ReplyToEvent.class.isAssignableFrom(arg.class)) {
+			callback = (Closure) callback.clone()
+			callback.delegate = new ReplyDecorator(arg.replyTo, (((Reactor.ReplyToEvent) arg).replyToObservable))
+		}
+		if (eventArg) {
+			callback arg
+		} else {
+			callback arg?.data
+		}
+	}
+
+	class ReplyDecorator {
+
+		final replyTo
+		final reactor.core.Observable observable
+
+		ReplyDecorator(replyTo, reactor.core.Observable observable) {
+			this.replyTo = replyTo
+			this.observable = observable
+		}
+
+
+		void reply() {
+			observable.notify(replyTo, new Event<Void>(Void))
+		}
+
+		void reply(data) {
+			observable.notify(replyTo, Event.wrap(data))
+		}
+	}
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureEventFunction.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureEventFunction.groovy
new file mode 100644
index 0000000..98c6e7b
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureEventFunction.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy.support
+
+import groovy.transform.CompileStatic
+import reactor.event.Event
+import reactor.function.Function
+
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ClosureEventFunction<K,V> implements Function<Event<K>,V> {
+
+	final Closure<V> callback
+	final boolean eventArg = true
+
+	ClosureEventFunction(Closure<V> cl) {
+		callback = cl
+		def argTypes = callback.parameterTypes
+		eventArg = Event.isAssignableFrom(argTypes[0])
+	}
+
+	@Override
+	V apply(Event<K> arg) {
+		if (eventArg) {
+			callback arg
+		} else {
+			callback arg.data
+		}
+	}
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureFunction.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureFunction.groovy
new file mode 100644
index 0000000..23c9291
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureFunction.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy.support
+
+import groovy.transform.CompileStatic
+import reactor.function.Function
+
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ClosureFunction<K,V> implements Function<K,V> {
+
+	final Closure<V> callback
+
+	public ClosureFunction(Closure cl) {
+		callback = cl
+	}
+
+	@Override
+	V apply(K t) {
+		callback t
+	}
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosurePredicate.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosurePredicate.groovy
new file mode 100644
index 0000000..f8dd06f
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosurePredicate.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.groovy.support
+
+import groovy.transform.CompileStatic
+import reactor.function.Predicate
+
+/**
+ * @author Jon Brisbin
+ */
+ at CompileStatic
+class ClosurePredicate<V> implements Predicate<V> {
+
+	final Closure<Boolean> callback
+
+	ClosurePredicate(Closure<Boolean> callback) {
+		this.callback = callback
+	}
+
+	@Override
+	boolean test(V value) {
+		callback value
+	}
+
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureReduce.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureReduce.groovy
new file mode 100644
index 0000000..d807127
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureReduce.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy.support
+
+import groovy.transform.CompileStatic
+import reactor.function.Function
+import reactor.tuple.Tuple2
+/**
+ * @author Stephane Maldini
+ * @author Jon Brisbin
+ */
+ at CompileStatic
+class ClosureReduce<T, V> implements Function<Tuple2<T, V>, V> {
+
+  final Closure<V> callback
+
+  ClosureReduce(Closure<V> cl) {
+    callback = cl
+  }
+
+  @Override
+  V apply(Tuple2<T, V> t) {
+
+    if (t.t2)
+      callback t.t1, t.t2
+    else
+      callback t.t1
+  }
+}
diff --git a/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureSupplier.groovy b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureSupplier.groovy
new file mode 100644
index 0000000..2370a86
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/groovy/reactor/groovy/support/ClosureSupplier.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy.support
+
+import groovy.transform.CompileStatic
+import reactor.function.Supplier
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ClosureSupplier<V> implements Supplier<V> {
+
+	final Closure<V> callback
+
+	ClosureSupplier(Closure cl) {
+		callback = cl
+	}
+
+	@Override
+	V get() {
+		callback()
+	}
+}
diff --git a/reactor-groovy-extensions/src/main/java/reactor/groovy/ext/package-info.java b/reactor-groovy-extensions/src/main/java/reactor/groovy/ext/package-info.java
new file mode 100644
index 0000000..93d3483
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/java/reactor/groovy/ext/package-info.java
@@ -0,0 +1,3 @@
+/**
+ * Reactor Groovy Extensions.
+ */
\ No newline at end of file
diff --git a/reactor-groovy-extensions/src/main/java/reactor/groovy/support/ClosureTupleConsumer.java b/reactor-groovy-extensions/src/main/java/reactor/groovy/support/ClosureTupleConsumer.java
new file mode 100644
index 0000000..bf3574f
--- /dev/null
+++ b/reactor-groovy-extensions/src/main/java/reactor/groovy/support/ClosureTupleConsumer.java
@@ -0,0 +1,26 @@
+package reactor.groovy.support;
+
+import groovy.lang.Closure;
+import reactor.function.Consumer;
+import reactor.tuple.Tuple;
+
+/**
+ * Invokes a {@link groovy.lang.Closure} using the contents of the incoming {@link reactor.tuple.Tuple} as the
+ * arguments.
+ *
+ * @author Jon Brisbin
+ */
+public class ClosureTupleConsumer implements Consumer<Tuple> {
+
+	private final Closure cl;
+
+	public ClosureTupleConsumer(Closure cl) {
+		this.cl = cl;
+	}
+
+	@Override
+	public void accept(Tuple tup) {
+		cl.call(tup.toArray());
+	}
+
+}
diff --git a/reactor-groovy/src/main/groovy/reactor/groovy/config/DSLUtils.groovy b/reactor-groovy/src/main/groovy/reactor/groovy/config/DSLUtils.groovy
new file mode 100644
index 0000000..c2070e6
--- /dev/null
+++ b/reactor-groovy/src/main/groovy/reactor/groovy/config/DSLUtils.groovy
@@ -0,0 +1,27 @@
+package reactor.groovy.config
+
+import groovy.transform.CompileStatic
+
+import static groovy.lang.Closure.DELEGATE_FIRST
+
+/**
+ * Author: smaldini
+ */
+ at CompileStatic
+class DSLUtils {
+
+	static public final Closure EMPTY_CLOSURE = {...args->}
+
+	/**
+	 * Helper for recurrent use-case : Delegating a closure to a builder and resolving it first
+	 * @param builder
+	 * @param closure
+	 * @return possible closure result
+	 */
+	static <T> T delegateFirstAndRun(builder, Closure<T> closure){
+		closure.delegate = builder
+		closure.resolveStrategy = DELEGATE_FIRST
+		closure()
+	}
+
+}
\ No newline at end of file
diff --git a/reactor-groovy/src/main/groovy/reactor/groovy/config/DispatcherConfigurationBuilder.groovy b/reactor-groovy/src/main/groovy/reactor/groovy/config/DispatcherConfigurationBuilder.groovy
new file mode 100644
index 0000000..c801d79
--- /dev/null
+++ b/reactor-groovy/src/main/groovy/reactor/groovy/config/DispatcherConfigurationBuilder.groovy
@@ -0,0 +1,30 @@
+package reactor.groovy.config
+
+import groovy.transform.CompileStatic
+import reactor.core.configuration.DispatcherConfiguration
+import reactor.core.configuration.DispatcherType
+import reactor.function.Supplier
+
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class DispatcherConfigurationBuilder implements Supplier<DispatcherConfiguration>{
+
+	final String name
+
+	DispatcherType type = DispatcherType.RING_BUFFER
+	Integer backlog
+	Integer size
+
+	private DispatcherConfiguration dispatcherConfiguration
+
+	DispatcherConfigurationBuilder(String name) {
+		this.name = name
+	}
+
+	@Override
+	DispatcherConfiguration get() {
+		return dispatcherConfiguration ?: new DispatcherConfiguration(name, type, backlog, size)
+	}
+}
diff --git a/reactor-groovy/src/main/groovy/reactor/groovy/config/EnvironmentBuilder.groovy b/reactor-groovy/src/main/groovy/reactor/groovy/config/EnvironmentBuilder.groovy
new file mode 100644
index 0000000..42589ea
--- /dev/null
+++ b/reactor-groovy/src/main/groovy/reactor/groovy/config/EnvironmentBuilder.groovy
@@ -0,0 +1,68 @@
+package reactor.groovy.config
+
+import groovy.transform.CompileStatic
+import reactor.core.Environment
+import reactor.core.configuration.ConfigurationReader
+import reactor.core.configuration.DispatcherConfiguration
+import reactor.core.configuration.ReactorConfiguration
+import reactor.event.dispatch.Dispatcher
+import reactor.function.Supplier
+
+import static groovy.lang.Closure.*
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class EnvironmentBuilder implements ConfigurationReader,Supplier<Environment> {
+
+	private final List<DispatcherConfiguration> dispatcherConfigurations = []
+	private final Map<String, List<Dispatcher>> dispatchers = [:]
+	private final Properties props
+	private Environment environment
+
+	String defaultDispatcher = Environment.RING_BUFFER
+
+	EnvironmentBuilder(Properties props) {
+		this.props = props
+	}
+
+	Environment get(){
+		   environment ?: new Environment(dispatchers, this)
+	}
+
+	@Override
+	ReactorConfiguration read() {
+		new ReactorConfiguration(dispatcherConfigurations, defaultDispatcher, props)
+	}
+
+	/**
+	 * initialize a Dispatcher
+	 * @param c DSL
+	 * @return {@link Dispatcher}
+	 */
+	DispatcherConfiguration dispatcher(String name,
+			@DelegatesTo(strategy = DELEGATE_FIRST, value = DispatcherConfigurationBuilder) Closure c
+	) {
+		def builder = new DispatcherConfigurationBuilder(name)
+		DSLUtils.delegateFirstAndRun builder, c
+
+		dispatcherConfigurations << builder.get()
+
+		builder.get()
+	}
+
+	/**
+	 * initialize a Dispatcher
+	 * @param c DSL
+	 * @return {@link Dispatcher}
+	 */
+	Dispatcher dispatcher(String name, Dispatcher d) {
+		def list = dispatchers[name]
+		if (!list) {
+			dispatchers[name] = list = []
+		}
+
+		list << d
+		d
+	}
+}
diff --git a/reactor-groovy/src/main/groovy/reactor/groovy/config/GroovyEnvironment.groovy b/reactor-groovy/src/main/groovy/reactor/groovy/config/GroovyEnvironment.groovy
new file mode 100644
index 0000000..063877f
--- /dev/null
+++ b/reactor-groovy/src/main/groovy/reactor/groovy/config/GroovyEnvironment.groovy
@@ -0,0 +1,185 @@
+package reactor.groovy.config
+
+import groovy.transform.CompileStatic
+import reactor.core.Environment
+import reactor.core.Reactor
+import reactor.event.dispatch.Dispatcher
+
+import static groovy.lang.Closure.*
+import groovy.transform.TypeCheckingMode
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder
+
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class GroovyEnvironment {
+
+	private final Map<String, ReactorBuilder> reactors = [:]
+
+	Environment reactorEnvironment
+
+	@CompileStatic(TypeCheckingMode.SKIP)
+	static GroovyEnvironment build(Reader r) {
+		def configuration = new CompilerConfiguration()
+		CompilerCustomizationBuilder.withConfig(configuration) {
+			ast(CompileStatic)
+		}
+
+		configuration.scriptBaseClass = ReactorScriptWrapper.name
+		def shell = new GroovyShell(configuration)
+		def script = shell.parse r
+		script.run()
+	}
+
+	static GroovyEnvironment build(File file) {
+		GroovyEnvironment gs = null
+		file.withReader { Reader reader ->
+			gs = build reader
+		}
+		gs
+	}
+
+	static GroovyEnvironment build(String script) {
+		def reader = new StringReader(script)
+		GroovyEnvironment gs = build reader
+		gs
+	}
+
+	/**
+	 * Root DSL to a GroovyEnvironment
+	 * @param c DSL
+	 * @return {@link GroovyEnvironment}
+	 */
+	static GroovyEnvironment create(@DelegatesTo(strategy = DELEGATE_FIRST, value = GroovyEnvironment) Closure c
+	) {
+		def configuration = new GroovyEnvironment()
+
+		DSLUtils.delegateFirstAndRun configuration, c
+
+		configuration
+	}
+
+
+	GroovyEnvironment include(GroovyEnvironment... groovyEnvironments) {
+		ReactorBuilder reactorBuilder
+		ReactorBuilder current
+		String key
+
+		for (groovyEnvironment in groovyEnvironments) {
+
+			if (reactorEnvironment) {
+				if (groovyEnvironment.reactorEnvironment) {
+					for (dispatcherEntry in groovyEnvironment.reactorEnvironment) {
+						for (dispatcher in dispatcherEntry.value) {
+							reactorEnvironment.addDispatcher(dispatcherEntry.key, dispatcher)
+						}
+					}
+				}
+			} else {
+				reactorEnvironment = groovyEnvironment.reactorEnvironment
+			}
+
+			for (reactorEntry in groovyEnvironment.reactors) {
+				reactorBuilder = ((Map.Entry<String, ReactorBuilder>) reactorEntry).value
+				key = ((Map.Entry<String, Reactor>) reactorEntry).key
+
+				current = reactors[key]
+				if (current) {
+					reactorBuilder.rehydrate current
+					reactorBuilder.addConsumersFrom current
+				} else {
+					reactors[key] = reactorBuilder
+				}
+			}
+
+		}
+		this
+	}
+
+	/**
+	 * initialize a Reactor
+	 * @param c DSL
+	 */
+	ReactorBuilder reactor(@DelegatesTo(strategy = DELEGATE_FIRST, value = ReactorBuilder) Closure c) {
+		reactor(null, c)
+	}
+
+	ReactorBuilder reactor(String name,
+	                       @DelegatesTo(strategy = DELEGATE_FIRST, value = ReactorBuilder) Closure c
+	) {
+		reactorEnvironment = reactorEnvironment ?: new Environment()
+
+		def builder = new ReactorBuilder(name, reactors)
+		builder.init()
+		builder.env = reactorEnvironment
+
+		DSLUtils.delegateFirstAndRun builder, c
+
+		builder
+	}
+
+	Reactor getAt(String reactor) {
+		reactors[reactor]?.get()
+	}
+
+	void putAt(String reactorName, Reactor reactor) {
+		ReactorBuilder builder = new ReactorBuilder(reactorName, reactors, reactor)
+		reactors[reactorName] = builder
+	}
+
+	Reactor reactor(String reactor) {
+		getAt reactor
+	}
+
+	Reactor reactor(String reactorName, Reactor reactor) {
+		putAt reactorName, reactor
+	}
+
+	Collection<ReactorBuilder> reactorBuildersByExtension(String extensionKey) {
+		reactors.findAll { String k, ReactorBuilder v -> v.ext(extensionKey) }.values()
+	}
+
+	ReactorBuilder reactorBuilder(String reactor) {
+		reactors[reactor]
+	}
+
+	ReactorBuilder reactorBuilder(String reactorName, ReactorBuilder reactor) {
+		reactors[reactorName] = reactor
+	}
+
+	/**
+	 * initialize a {@link Environment}
+	 * @param c DSL
+	 */
+	Environment environment(@DelegatesTo(strategy = DELEGATE_FIRST, value = EnvironmentBuilder) Closure c) {
+		environment([:], c)
+	}
+
+	Environment environment(Map properties,
+	                        @DelegatesTo(strategy = DELEGATE_FIRST, value = EnvironmentBuilder) Closure c
+	) {
+		def builder = new EnvironmentBuilder(properties as Properties)
+		DSLUtils.delegateFirstAndRun builder, c
+		reactorEnvironment = builder.get()
+	}
+
+	Environment environment(Environment environment) {
+		this.reactorEnvironment = environment
+	}
+
+	Environment environment() {
+		this.reactorEnvironment
+	}
+
+	Dispatcher dispatcher(String dispatcher) {
+		reactorEnvironment?.getDispatcher(dispatcher)
+	}
+
+	Dispatcher dispatcher(String dispatcherName, Dispatcher dispatcher) {
+		reactorEnvironment?.addDispatcher(dispatcherName, dispatcher)
+		dispatcher
+	}
+
+}
diff --git a/reactor-groovy/src/main/groovy/reactor/groovy/config/ReactorBuilder.groovy b/reactor-groovy/src/main/groovy/reactor/groovy/config/ReactorBuilder.groovy
new file mode 100644
index 0000000..08edd88
--- /dev/null
+++ b/reactor-groovy/src/main/groovy/reactor/groovy/config/ReactorBuilder.groovy
@@ -0,0 +1,361 @@
+package reactor.groovy.config
+
+import groovy.transform.CompileStatic
+import reactor.convert.Converter
+import reactor.core.Environment
+import reactor.core.Reactor
+import reactor.core.composable.Deferred
+import reactor.core.composable.Stream
+import reactor.core.composable.spec.Streams
+import reactor.core.spec.Reactors
+import reactor.event.Event
+import reactor.event.dispatch.Dispatcher
+import reactor.event.routing.ArgumentConvertingConsumerInvoker
+import reactor.event.routing.ConsumerInvoker
+import reactor.event.routing.EventRouter
+import reactor.event.selector.ObjectSelector
+import reactor.event.selector.Selector
+import reactor.event.selector.Selectors
+import reactor.event.support.CallbackEvent
+import reactor.filter.Filter
+import reactor.filter.FirstFilter
+import reactor.filter.PassThroughFilter
+import reactor.filter.RandomFilter
+import reactor.filter.RoundRobinFilter
+import reactor.function.Consumer
+import reactor.function.Predicate
+import reactor.function.Supplier
+import reactor.groovy.support.ClosureEventConsumer
+
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ReactorBuilder implements Supplier<Reactor> {
+
+	static final private Selector noSelector = Selectors.anonymous()
+	static final private Filter DEFAULT_FILTER = new PassThroughFilter()
+
+	static final String ROUND_ROBIN = 'round-robin'
+	static final String PUB_SUB = 'all'
+	static final String RANDOM = 'random'
+	static final String FIRST = 'first'
+
+	Environment env
+	Converter converter
+	EventRouter router
+	ConsumerInvoker consumerInvoker
+	Dispatcher dispatcher
+	Filter filter
+	boolean override = false
+
+	private String dispatcherName
+
+	final String name
+
+	private final SortedSet<HeadAndTail> streams = new TreeSet<HeadAndTail>()
+	private final Map<String, Object> ext = [:]
+	private final Map<Selector, List<Consumer>> consumers = [:]
+	private final Map<String, ReactorBuilder> reactorMap
+	private final List<ReactorBuilder> childNodes = []
+	private Reactor reactor
+
+	ReactorBuilder(String name, Map<String, ReactorBuilder> reactorMap) {
+		this.reactorMap = reactorMap
+		this.name = name
+	}
+
+	ReactorBuilder(String name, Map<String, ReactorBuilder> reactorMap, Reactor reactor) {
+		this(name, reactorMap)
+		this.reactor = reactor
+	}
+
+	void rehydrate(ReactorBuilder r) {
+		converter = converter ?: r.converter
+		filter = filter ?: r.filter
+		dispatcher = dispatcher ?: r.dispatcher
+		dispatcherName = dispatcherName ?: r.dispatcherName
+		router = router ?: r.router
+		consumerInvoker = consumerInvoker ?: r.consumerInvoker
+
+		if (!override) {
+			streams.addAll r.streams
+			childNodes.addAll r.childNodes
+		}
+
+		for (entry in r.ext) {
+			if (!ext[((Map.Entry<String, Object>) entry).key]) ext[((Map.Entry<String, Object>) entry).key] =
+					((Map.Entry<String, Object>) entry).value
+		}
+	}
+
+	void init() {
+		if (name) {
+			def r = reactorMap[name]
+			if (r) {
+				rehydrate r
+				addConsumersFrom r
+			}
+			reactorMap[name] = this
+		}
+	}
+
+	def ext(String k) {
+		ext[k]
+	}
+
+	void ext(String k, v) {
+		ext[k] = v
+	}
+
+	void exts(Map<String, Object> map) {
+		ext.putAll map
+	}
+
+	Filter routingStrategy(String strategy) {
+		switch (strategy) {
+			case ROUND_ROBIN:
+				filter = new RoundRobinFilter()
+				break
+			case RANDOM:
+				filter = new RandomFilter()
+				break
+			case FIRST:
+				filter = new FirstFilter()
+				break
+			case PUB_SUB:
+			default:
+				filter = DEFAULT_FILTER
+		}
+	}
+
+	ReactorBuilder dispatcher(String dispatcher) {
+		this.dispatcherName = dispatcher
+		this.dispatcher = env?.getDispatcher(dispatcher)
+		this
+	}
+
+
+
+	ReactorBuilder on(@DelegatesTo(strategy = Closure.DELEGATE_FIRST,
+			value = ClosureEventConsumer.ReplyDecorator) Closure closure) {
+		on noSelector, new ClosureEventConsumer((Closure) closure.clone())
+	}
+
+	ReactorBuilder on(String selector, @DelegatesTo(strategy = Closure.DELEGATE_FIRST,
+			value = ClosureEventConsumer.ReplyDecorator) Closure closure) {
+		on Selectors.object(selector), new ClosureEventConsumer((Closure) closure.clone())
+	}
+
+	ReactorBuilder on(Consumer consumer) {
+		on noSelector, consumer
+	}
+
+	ReactorBuilder on(String selector, Consumer closure) {
+		on Selectors.object(selector), closure
+	}
+
+	ReactorBuilder on(Selector selector, @DelegatesTo(strategy = Closure.DELEGATE_FIRST,
+			value = ClosureEventConsumer.ReplyDecorator) Closure closure) {
+		on selector, new ClosureEventConsumer((Closure) closure.clone())
+	}
+
+	ReactorBuilder on(Selector selector, Consumer closure) {
+		consumers[selector] = consumers[selector] ?: (List<Consumer>) []
+		consumers[selector] << closure
+		this
+	}
+
+	void stream(@DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = Stream) Closure<Stream> closure) {
+		stream((Selector) null, closure)
+	}
+
+	void stream(Deferred<Event<?>, Stream<Event<?>>> head, Stream<Event<?>> tail) {
+		stream((Selector) null, head, tail)
+	}
+
+	void stream(String selector, @DelegatesTo(strategy = Closure.DELEGATE_FIRST,
+			value = Stream) Closure<Stream> closure) {
+		stream Selectors.$(selector), closure
+	}
+
+	void stream(Selector selector, @DelegatesTo(strategy = Closure.DELEGATE_FIRST,
+			value = Stream) Closure<Stream> closure) {
+		Deferred<Event<?>, Stream<Event<?>>> head = Streams.<Event<?>> defer().get()
+		Stream newTail = DSLUtils.delegateFirstAndRun(head.compose(), closure)
+		stream selector, head, newTail
+	}
+
+
+	void stream(Selector selector, Deferred<Event<?>, Stream<Event<?>>> head, Stream<Event<?>> tail) {
+		if (tail) {
+			streams << new HeadAndTail(head, tail, selector)
+		}
+	}
+
+	@Override
+	Reactor get() {
+		if (reactor)
+			return reactor
+
+		def spec = Reactors.reactor().env(env)
+		if (dispatcherName) {
+			spec.dispatcher(dispatcherName)
+		} else {
+			spec.dispatcher(dispatcher)
+		}
+		if (converter) {
+			spec.converters(converter)
+		}
+		if (router) {
+			spec.eventRouter(router)
+		} else if (streams) {
+			Deferred<Event<?>, Stream<Event<?>>> deferred = null
+			Stream<Event<?>> tail = null
+			HeadAndTail stream
+			HeadAndTail anticipatedStream
+			Iterator<HeadAndTail> it = streams.iterator()
+			boolean first = true
+
+			while (it.hasNext() || anticipatedStream) {
+				stream = anticipatedStream ?: it.next()
+				anticipatedStream = null
+
+				if (first) {
+					first = false
+					deferred = Streams.<Event<?>> defer().get()
+					tail = deferred.compose()
+				}
+				if (stream.selector) {
+					if (it.hasNext()) {
+						anticipatedStream = it.next()
+					} else {
+						def finalDeferred = Streams.<Event<?>> defer().get()
+						anticipatedStream = new HeadAndTail(finalDeferred, finalDeferred.compose(), null)
+					}
+					tail.filter(new EventRouterPredicate(stream.selector), anticipatedStream.tail).connect(stream.head.compose())
+				} else {
+					tail.connect(stream.head.compose())
+				}
+				tail = stream.tail
+			}
+
+			tail.consumeEvent(new Consumer<Event>() {
+				@Override
+				void accept(Event eventEvent) {
+					if (eventEvent.class == CallbackEvent)
+						((CallbackEvent) eventEvent).callback()
+				}
+			})
+			spec.eventRouter(new StreamEventRouter(filter ?: DEFAULT_FILTER,
+					consumerInvoker ?: new ArgumentConvertingConsumerInvoker(converter), deferred))
+
+		} else {
+			if (filter) {
+				spec.eventFilter(filter)
+			}
+			if (consumerInvoker) {
+				spec.consumerInvoker(consumerInvoker)
+			}
+		}
+
+		reactor = spec.get()
+
+		if (childNodes) {
+			for (childNode in childNodes) {
+				childNode.get()
+			}
+		}
+
+		if (consumers) {
+			for (perSelectorConsumers in consumers.entrySet()) {
+				for (consumer in perSelectorConsumers.value) {
+					reactor.on((Selector) perSelectorConsumers.key, consumer)
+				}
+			}
+		}
+
+		reactor
+	}
+
+	void addConsumersFrom(ReactorBuilder from) {
+		Map<Selector, List<Consumer>> fromConsumers = from.consumers
+		Map.Entry<Selector, List<Consumer>> consumerEntry
+
+		for (_consumerEntry in fromConsumers) {
+			consumerEntry = (Map.Entry<Selector, List<Consumer>>) _consumerEntry
+			for (consumer in consumerEntry.value) {
+				on consumerEntry.key, (Consumer) consumer
+			}
+		}
+	}
+
+/**
+ * initialize a Reactor
+ * @param c DSL
+ */
+	ReactorBuilder reactor(
+			@DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = NestedReactorBuilder)
+			Closure c
+	) {
+		reactor(null, c)
+	}
+
+	ReactorBuilder reactor(String reactorName,
+	                       @DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = NestedReactorBuilder)
+	                       Closure c
+	) {
+		def builder = new NestedReactorBuilder(reactorName, this, reactor)
+		builder.init()
+
+		DSLUtils.delegateFirstAndRun builder, c
+
+		childNodes << builder
+		builder
+	}
+
+	@CompileStatic
+	private final class EventRouterPredicate implements Predicate<Event<?>> {
+		final Selector sel
+
+		EventRouterPredicate(Selector sel) {
+			this.sel = sel
+		}
+
+		@Override
+		boolean test(Event<?> event) {
+			sel.matches(event.headers.get(StreamEventRouter.KEY_HEADER))
+		}
+	}
+
+	@CompileStatic
+	final class HeadAndTail implements Comparable<HeadAndTail> {
+		final Deferred<Event<?>, Stream<Event<?>>> head
+		final Stream<Event<?>> tail
+		final Selector selector
+
+		HeadAndTail(Deferred<Event<?>, Stream<Event<?>>> head, Stream<Event<?>> tail, Selector selector) {
+			this.head = head
+			this.tail = tail
+			this.selector = selector
+		}
+
+		@Override
+		int compareTo(HeadAndTail o) {
+			selector ? 1 : 0
+		}
+	}
+
+	@CompileStatic
+	final class NestedReactorBuilder extends ReactorBuilder {
+
+		NestedReactorBuilder(String reactorName, ReactorBuilder parent, Reactor reactor) {
+			super(reactorName, parent.reactorMap, reactor)
+			rehydrate parent
+
+			env = parent.env
+			consumers.putAll(parent.consumers)
+		}
+	}
+
+}
diff --git a/reactor-groovy/src/main/groovy/reactor/groovy/config/ReactorScriptWrapper.groovy b/reactor-groovy/src/main/groovy/reactor/groovy/config/ReactorScriptWrapper.groovy
new file mode 100644
index 0000000..ded3b07
--- /dev/null
+++ b/reactor-groovy/src/main/groovy/reactor/groovy/config/ReactorScriptWrapper.groovy
@@ -0,0 +1,21 @@
+package reactor.groovy.config
+
+import groovy.transform.CompileStatic
+
+import static groovy.lang.Closure.DELEGATE_FIRST
+
+/**
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class ReactorScriptWrapper extends Script {
+
+	GroovyEnvironment doWithReactor(@DelegatesTo(strategy = DELEGATE_FIRST, value = GroovyEnvironment) Closure c) {
+		GroovyEnvironment.create c
+	}
+
+	@Override
+	Object run() {
+		super.run()
+	}
+}
diff --git a/reactor-groovy/src/main/java/reactor/groovy/config/StreamEventRouter.java b/reactor-groovy/src/main/java/reactor/groovy/config/StreamEventRouter.java
new file mode 100644
index 0000000..7563459
--- /dev/null
+++ b/reactor-groovy/src/main/java/reactor/groovy/config/StreamEventRouter.java
@@ -0,0 +1,90 @@
+package reactor.groovy.config;
+
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Stream;
+import reactor.event.Event;
+import reactor.event.registry.Registration;
+import reactor.event.routing.ConsumerFilteringEventRouter;
+import reactor.event.routing.ConsumerInvoker;
+import reactor.event.support.CallbackEvent;
+import reactor.filter.Filter;
+import reactor.function.Consumer;
+import reactor.function.support.CancelConsumerException;
+
+import java.util.List;
+
+/**
+ * @author Stephane Maldini
+ */
+public class StreamEventRouter extends ConsumerFilteringEventRouter {
+
+	public static final String KEY_HEADER = "___key";
+
+	private final Deferred<Event<?>, Stream<Event<?>>> stream;
+
+	public StreamEventRouter(Filter filter, ConsumerInvoker consumerInvoker, Deferred<Event<?>,
+			Stream<Event<?>>> stream) {
+		super(filter, consumerInvoker);
+		this.stream = stream;
+	}
+
+	@Override
+	public void route(final Object key, final Event<?> event,
+	                  final List<Registration<? extends Consumer<? extends Event<?>>>> consumers,
+	                  final Consumer<?> completionConsumer, final Consumer<Throwable> errorConsumer) {
+
+		try {
+			event.getHeaders().set(KEY_HEADER, key.toString());
+		} catch (Exception e) {
+			//ignore
+		}
+
+		stream.acceptEvent(new CallbackEvent<Event<?>>(
+				event.getHeaders(),
+				event,
+				new Consumer<Event<?>>() {
+					@Override
+					public void accept(Event<?> _event) {
+						Event<?> hydratedEvent = event.copy(_event != null ? _event.getData() : null);
+						if (null != consumers) {
+							for (Registration<? extends Consumer<? extends Event<?>>> reg : getFilter().filter(consumers, key)) {
+								if (reg.isCancelled() || reg.isPaused()) {
+									continue;
+								}
+								try {
+									if (null != reg.getSelector().getHeaderResolver()) {
+										event.getHeaders().setAll(reg.getSelector().getHeaderResolver().resolve(key));
+									}
+									getConsumerInvoker().invoke(reg.getObject(), Void.TYPE, event);
+								} catch (CancelConsumerException cancel) {
+									reg.cancel();
+								} catch (Throwable t) {
+									if (null != hydratedEvent.getErrorConsumer()) {
+										hydratedEvent.consumeError(t);
+									} else if (null != errorConsumer) {
+										errorConsumer.accept(t);
+									}
+									stream.accept(t);
+								} finally {
+									if (reg.isCancelAfterUse()) {
+										reg.cancel();
+									}
+								}
+							}
+						}
+						if (null != completionConsumer) {
+							try {
+								getConsumerInvoker().invoke(completionConsumer, Void.TYPE, hydratedEvent);
+							} catch (Exception e) {
+								if (null != errorConsumer) {
+									errorConsumer.accept(e);
+								}
+								stream.accept(e);
+							}
+						}
+					}
+				}
+		));
+	}
+
+}
diff --git a/reactor-groovy/src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule b/reactor-groovy/src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
new file mode 100644
index 0000000..4e14b42
--- /dev/null
+++ b/reactor-groovy/src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+moduleName=reactor-groovy-module
+moduleVersion=1.0
+extensionClasses=reactor.groovy.ext.ObservableExtensions,reactor.groovy.ext.ComposableExtensions,\
+  reactor.groovy.ext.ProcessorExtensions
+staticExtensionClasses=reactor.groovy.ext.ReactorStaticExtensions
\ No newline at end of file
diff --git a/reactor-groovy/src/test/groovy/reactor/groovy/CompileStaticTest.groovy b/reactor-groovy/src/test/groovy/reactor/groovy/CompileStaticTest.groovy
new file mode 100644
index 0000000..40e1f51
--- /dev/null
+++ b/reactor-groovy/src/test/groovy/reactor/groovy/CompileStaticTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy
+
+import groovy.transform.CompileStatic
+import reactor.core.Environment
+import reactor.function.Supplier
+
+/**
+ * This class shouldnt fail compilation
+ *
+ * @author Stephane Maldini
+ */
+ at CompileStatic
+class CompileStaticTest {
+
+	Environment env = new Environment()
+
+	def run() {
+
+		def testClosure = {
+			'test'
+		}
+
+		def supplier = new Supplier<String>(){
+			@Override
+			String get() {
+				'test'
+			}
+		}
+
+		//P.task(supplier).using(env)
+	}
+
+	static void main(String[] args) {
+		new CompileStaticTest().run()
+
+	}
+}
diff --git a/reactor-groovy/src/test/groovy/reactor/groovy/GroovyConfigurationSpec.groovy b/reactor-groovy/src/test/groovy/reactor/groovy/GroovyConfigurationSpec.groovy
new file mode 100644
index 0000000..358eb1f
--- /dev/null
+++ b/reactor-groovy/src/test/groovy/reactor/groovy/GroovyConfigurationSpec.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.groovy
+
+import reactor.core.Environment
+import reactor.event.Event
+import reactor.event.dispatch.SynchronousDispatcher
+import reactor.groovy.config.GroovyEnvironment
+import reactor.groovy.support.ClosureEventConsumer
+import spock.lang.Ignore
+import spock.lang.Specification
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import static reactor.event.selector.Selectors.$
+
+/**
+ * @author Stephane Maldini (smaldini)
+ */
+class GroovyConfigurationSpec extends Specification {
+
+	def "GroovyEnvironment creates dispatcher properly"() {
+		when:
+			"Building a simple dispatcher"
+			Environment env = StaticConfiguration.test().environment()
+		then:
+			env.defaultDispatcher == env.getDispatcher('test')
+	}
+
+	def "GroovyEnvironment creates reactor properly"() {
+		when:
+			"Building a simple dispatcher"
+			GroovyEnvironment groovySystem = StaticConfiguration.test2()
+		then:
+			groovySystem['test1']
+			groovySystem['child_test1']
+	}
+
+	def "GroovyEnvironment creates consumers properly"() {
+		when:
+			"Building a simple dispatcher"
+			GroovyEnvironment groovySystem = StaticConfiguration.test3()
+			def res = null
+			def latch = new CountDownLatch(1)
+			groovySystem['test1'].send('test', 'test') {
+				res = it
+				latch.countDown()
+			}
+		then:
+			latch.await(5, TimeUnit.SECONDS)
+			groovySystem['test1'].dispatcher instanceof SynchronousDispatcher
+			res
+	}
+
+	def "GroovyEnvironment includes another Environment"() {
+		when:
+			"Building a simple dispatcher"
+			GroovyEnvironment groovySystem = StaticConfiguration.test4()
+			def res = null
+			def latch = new CountDownLatch(1)
+			groovySystem['test1'].send('test', 'test') {
+				res = it
+				latch.countDown()
+			}
+		then:
+			latch.await(5, TimeUnit.SECONDS)
+			groovySystem.dispatcher('testDispatcher') instanceof SynchronousDispatcher
+			groovySystem['test1'].dispatcher == groovySystem.dispatcher('testDispatcher')
+			groovySystem['test2'].dispatcher == groovySystem.dispatcher('testDispatcher')
+			res
+	}
+
+	def "GroovyEnvironment filters per extension"() {
+		when:
+			"Building a simple dispatcher"
+			GroovyEnvironment groovySystem = StaticConfiguration.test2()
+
+		then:
+			groovySystem.reactorBuildersByExtension('a').size() == 2
+	}
+
+	@Ignore
+	def "GroovyEnvironment intercept with Stream properly"() {
+		when:
+			"Building a simple dispatcher"
+			GroovyEnvironment groovySystem = StaticConfiguration.test5()
+			def res = null
+			def replyTo = $()
+			def consumer = new ClosureEventConsumer({ res = it })
+			groovySystem['test1'].on replyTo, consumer
+			groovySystem['test1'].send 'test', Event.wrap('test').setReplyTo(replyTo.object)
+		then:
+			groovySystem['test1'].dispatcher instanceof SynchronousDispatcher
+			res == 'intercepted twice'
+		when:
+			res = null
+			replyTo = $()
+			groovySystem['test1'].on replyTo, consumer
+			groovySystem['test1'].send 'test2', Event.wrap('test').setReplyTo(replyTo.object)
+		then:
+			res == 'intercepted'
+		when:
+			res = null
+			replyTo = $()
+			groovySystem['test2'].on replyTo, consumer
+			groovySystem['test2'].send 'test', Event.wrap('test').setReplyTo(replyTo.object)
+		then:
+			res == null
+		when:
+			res = null
+			replyTo = $()
+			groovySystem['test2'].on replyTo, consumer
+			groovySystem['test2'].send 'test2', Event.wrap('test').setReplyTo(replyTo.object)
+		then:
+			res == 'test'
+	}
+
+}
diff --git a/reactor-groovy/src/test/groovy/reactor/groovy/GroovyPromisesSpec.groovy b/reactor-groovy/src/test/groovy/reactor/groovy/GroovyPromisesSpec.groovy
new file mode 100644
index 0000000..f2894b8
--- /dev/null
+++ b/reactor-groovy/src/test/groovy/reactor/groovy/GroovyPromisesSpec.groovy
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package reactor.groovy
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import reactor.core.Environment
+import reactor.core.composable.Deferred
+import reactor.core.composable.Promise
+import reactor.core.composable.spec.Promises
+import reactor.event.dispatch.EventLoopDispatcher
+import spock.lang.Shared
+import spock.lang.Specification
+/**
+ * @author Stephane Maldini
+ */
+class GroovyPromisesSpec extends Specification {
+
+	@Shared def testEnv
+
+	void setupSpec(){
+		testEnv = new Environment()
+		testEnv.addDispatcher('eventLoop',new EventLoopDispatcher('eventLoop', 256))
+	}
+
+	def "Promise returns value"() {
+		when: "a deferred Promise"
+		def p = Promises.success("Hello World!").get()
+
+		then: 'Promise contains value'
+		p.get() == "Hello World!"
+	}
+
+
+	def "Promise from Closure"() {
+		when: "a deferred Promise"
+		Promise<String> p = Promises.task{"Hello World!"}.get()
+
+		then: 'Promise contains value'
+		p.await() == "Hello World!"
+
+		when: "a deferred Promise"
+		p = Promise.<String>from{"Hello World!"}.get()
+
+		then: 'Promise contains value'
+		p.await() == "Hello World!"
+	}
+
+	def "Compose from Closure"() {
+		when:
+			'Defer a composition'
+			def c = Promises.task { sleep 500; 1 } get()
+
+		and:
+			'apply a transformation'
+			def d = c | { it + 1 }
+
+		then:
+			'Composition contains value'
+			d.await() == 2
+	}
+
+	def "Promise notifies of Failure"() {
+		when: "a deferred failed Promise"
+		Promise p = Promises.error(new Exception("Bad code! Bad!")).get()
+
+		and: "invoke result"
+		p.get()
+
+		then:
+		p.error
+		thrown(RuntimeException)
+
+		when: "a deferred failed Promise with runtime exception"
+			 p = Promises.error(new IllegalArgumentException("Bad code! Bad!")).get()
+
+		and: "invoke result"
+			p.get()
+
+		then:
+			p.error
+			thrown(IllegalArgumentException)
+	}
+
+	def "Promises can be mapped"() {
+		given: "a synchronous promise"
+		Deferred p = Promises.defer().get()
+
+		when: "add a mapping closure"
+		Promise s = p | { Integer.parseInt it }
+
+		and: "setting a value"
+		p << '10'
+
+		then:
+		s.get() == 10
+
+		when: "add a mapping closure"
+		p = Promises.defer().get()
+		s = p.compose().then { Integer.parseInt it }
+
+		and: "setting a value"
+		p << '10'
+
+		then:
+		s.get() == 10
+	}
+
+	def "A promise can be be consumed by another promise"() {
+		given: "two synchronous promises"
+		Deferred p1 = Promises.defer().get()
+		Deferred p2 = Promises.defer().get()
+
+		when: "p1 is consumed by p2"
+		p1 << p2 //p1.consume p2
+
+		and: "setting a value"
+		p1 << 'Hello World!'
+
+		then: 'P2 consumes the value when P1'
+		p2.compose().get() == 'Hello World!'
+	}
+
+
+
+	def "Errors stop compositions"() {
+		given: "a promise"
+		Deferred p = Promises.defer().env(testEnv).dispatcher('eventLoop').get()
+		final latch = new CountDownLatch(1)
+
+		when: "p1 is consumed by p2"
+		Promise s = p.compose().then{ Integer.parseInt it }.
+				when (NumberFormatException, { latch.countDown() }).
+				then{ println('not in log'); true }
+
+		and: "setting a value"
+		p << 'not a number'
+		s.await(2000, TimeUnit.MILLISECONDS)
+
+		then: 'No value'
+		thrown(NumberFormatException)
+		latch.count == 0
+	}
+
+	def "Promise compose after set"() {
+		given: "a synchronous promise"
+		def p = Promises.success('10').get()
+
+		when: "composing 2 functions"
+		def s = p | { Integer.parseInt it } | { it*10 }
+
+		then:
+		s.get() == 100
+	}
+
+}
diff --git a/reactor-groovy/src/test/groovy/reactor/groovy/GroovyReactorSpec.groovy b/reactor-groovy/src/test/groovy/reactor/groovy/GroovyReactorSpec.groovy
new file mode 100644
index 0000000..b8c9c0c
--- /dev/null
+++ b/reactor-groovy/src/test/groovy/reactor/groovy/GroovyReactorSpec.groovy
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.groovy
+
+import groovy.transform.CompileStatic
+import reactor.core.Reactor
+
+import static reactor.event.selector.Selectors.$
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import reactor.core.Environment
+import reactor.core.spec.Reactors
+import reactor.event.Event
+import reactor.event.dispatch.EventLoopDispatcher
+import spock.lang.Shared
+import spock.lang.Specification
+
+/**
+ * @author Stephane Maldini (smaldini)
+ */
+class GroovyReactorSpec extends Specification {
+
+	@Shared def testEnv
+
+	void setupSpec(){
+		testEnv = new Environment()
+		testEnv.addDispatcher('eventLoop',new EventLoopDispatcher('eventLoop', 256))
+	}
+
+	def "Groovy Reactor dispatches events properly"() {
+
+		given: "a simple reactor implementation"
+		def r1 = Reactors.reactor().get()
+		def r2 = Reactors.reactor().get()
+		def latch = new CountDownLatch(1)
+
+		when: 'Using simple arguments'
+		def result = ""
+		r1.on('test2') { String s ->
+			result = s
+			latch.countDown()
+		}
+		r1.notify 'test2', 'Hello'
+
+		then:
+		latch.await(5, TimeUnit.SECONDS)
+		result == 'Hello'
+
+		when: 'Using Selector and Consumer<Event> arguments'
+		def data = ""
+		def header = ""
+		latch = new CountDownLatch(1)
+
+		r2.on($('test')) { Event<String> s ->
+			data = s.data
+			header = s.headers['someHeader']
+			latch.countDown()
+		}
+		r2.notify for: 'test', data: 'Hello World!', someHeader: 'test'
+
+		then:
+		latch.await(5, TimeUnit.SECONDS)
+		data == "Hello World!"
+		header == "test"
+
+	}
+
+	def "Groovy Reactor provides Closure as Supplier on notify"() {
+
+		given: "a simple Reactor"
+		def r = Reactors.reactor().get()
+		def result = ""
+		r.on('supplier') { String s ->
+			result = s
+		}
+
+		when: "a supplier is provided"
+		r.notify('supplier', { "Hello World!" })
+
+		then: "the result has been set"
+		result == "Hello World!"
+
+	}
+
+	def "Groovy Reactor allows inline reply"() {
+
+		given: "a simple reactor implementation"
+		def reactor = Reactors.reactor().get()
+
+		when: 'Using simple arguments'
+		def data2 = ""
+		reactor.on($('test')){ String s ->
+			reply(s + ' ok')
+		}  // ugly hack until I can get Groovy Closure invocation support built-in
+
+		reactor.send('test', 'send'){
+			data2 = 'test3'
+		}
+
+		then:
+		data2 == 'test3'
+
+	}
+
+	def "Compile Static Reactor"(){
+		given:
+			final reactor.core.Environment env = new reactor.core.Environment()
+
+			final Reactor reactor = Reactors.reactor()
+					.env(env) // our current Environment
+					.dispatcher(Environment.THREAD_POOL)
+					.get()
+
+		when:
+			"A simple scenario"
+
+			def consumer = new Consumer(r:reactor)
+			consumer.setupMessages()
+			def producer = new Producer(r:reactor)
+			producer.makeNoise('Yeah we is awesome')
+			consumer.result.await()
+
+		then:
+			consumer.result.count == 0
+	}
+
+	//FIXME Groovy issue -> invokes Reactor.notify(Object key) instead of Observable.extensions(Observable self,
+	// Map params)
+	//@CompileStatic
+	class Producer{
+		Reactor r
+		void makeNoise(String noise){
+			r.notify for: 'makeNoise', data: noise
+		}
+	}
+
+	class Consumer{
+		Reactor r
+		def result = new CountDownLatch(1)
+
+		void setupMessages(){
+			r.on($('makeNoise')) { String noise ->
+				println noise
+				result.countDown()
+			}
+		}
+	}
+
+}
diff --git a/reactor-groovy/src/test/groovy/reactor/groovy/GroovyStreamSpec.groovy b/reactor-groovy/src/test/groovy/reactor/groovy/GroovyStreamSpec.groovy
new file mode 100644
index 0000000..114867b
--- /dev/null
+++ b/reactor-groovy/src/test/groovy/reactor/groovy/GroovyStreamSpec.groovy
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.groovy
+
+import static reactor.event.selector.Selectors.$
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import reactor.core.Environment
+import reactor.core.composable.Stream
+import reactor.core.composable.spec.Streams
+import reactor.core.spec.Reactors
+import reactor.event.dispatch.EventLoopDispatcher
+import reactor.function.support.Tap
+import spock.lang.Shared
+import spock.lang.Specification
+
+/**
+ * @author Stephane Maldini
+ */
+class GroovyStreamSpec extends Specification {
+
+	@Shared
+	def testEnv
+
+	void setupSpec() {
+		testEnv = new Environment()
+		testEnv.addDispatcher('eventLoop', new EventLoopDispatcher('eventLoop', 256))
+	}
+
+
+	def "Compose from multiple values"() {
+		when:
+			'Defer a composition'
+			Stream s = Streams.defer(['1', '2', '3', '4', '5']).get()
+
+		and:
+			'apply a transformation'
+			int sum = 0
+			Stream d = s | { Integer.parseInt it } | { sum += it; sum }
+
+		then:
+			d.flush()
+			sum == 15
+	}
+
+	def "Compose from multiple filtered values"() {
+		when:
+			'Defer a composition'
+			def c = Streams.defer(['1', '2', '3', '4', '5']).get()
+
+		and:
+			'apply a transformation that filters odd elements'
+			def t = new Tap()
+			def d = ((c | { Integer.parseInt it }) & { it % 2 == 0 }) << t
+
+		then:
+			d.flush()
+			t.get() == 4
+	}
+
+	def "Error handling with composition from multiple values"() {
+		when:
+			'Defer a composition'
+			def c = Streams.defer(['1', '2', '3', '4', '5']).get()
+
+		and:
+			'apply a transformation that generates an exception for the last value'
+			int sum = 0
+			def t = new Tap()
+			def d = c | { Integer.parseInt it } | { if (it >= 5) throw new IllegalArgumentException() else sum += it }
+			d << t
+
+		then:
+			d.flush()
+			t.get() == 10
+	}
+
+
+	def "Reduce composition from multiple values"() {
+		when:
+			'Defer a composition'
+			def c = Streams.defer(['1', '2', '3', '4', '5']).get()
+
+		and:
+			'apply a reduction'
+			def d = (c | { Integer.parseInt it }) % { i, acc = 1 -> acc * i;  }
+			def t = d.tap()
+			d.flush()
+
+		then:
+			t.get() == 120
+	}
+
+
+	def "consume first and last with a composition from multiple values"() {
+		when:
+			'Defer a composition'
+			def c = Streams.defer(['1', '2', '3', '4', '5']).get()
+
+		and:
+			'apply a transformation'
+			Stream d = c | { Integer.parseInt it }
+
+		and:
+			'reference first and last'
+			def first = d.first().tap()
+			def last = d.last().tap()
+
+			d.flush()
+
+		then:
+			first.get() == 1
+			last.get() == 5
+	}
+
+	/*def "Compose events (Request/Reply)"() {
+		given:
+			'a reactor and a selector'
+			def r = Reactors.reactor().using(testEnv).dispatcher('eventLoop').get()
+			def key = $()
+
+		when:
+			'register a Reply Consumer'
+			r.receive(key.t1) { String test ->
+				Integer.parseInt test
+			}
+
+		and:
+			'compose the event'
+			def c = r.compose(key.t2, '1') % { i, acc = [] -> acc << i }
+
+		then:
+			c.awaitNext(1, TimeUnit.SECONDS)
+			c.get() == [1]
+	}*/
+
+	/* def "Compose events (Request/ N Replies)"() {
+			given:
+				'a reactor and a selector'
+				def r = Reactors.reactor().using(testEnv).dispatcher('eventLoop').get()
+				def key = $()
+
+			when:
+				'register a Reply Consumer'
+				r.receive(key.t1) { String test ->
+					Integer.parseInt test
+				}
+				r.receive(key.t1) { String test ->
+					(Integer.parseInt(test)) * 100
+				}
+
+				r.receive(key.t1) { String test ->
+					(Integer.parseInt(test)) * 1000
+				}
+
+			and:
+				'prepare reduce and notify composition'
+				def c1 = Streams.defer().using(r).get()
+				def c2 = c1.take(2).reduce { i, acc = [] -> acc << i }
+
+				r.compose(key.t2, '1', c1)
+
+			then:
+				c2.awaitNext(1, TimeUnit.SECONDS)
+				c2.get() == [1, 100]
+
+			when:
+				'using reduce() alias'
+				c1 = Streams.defer().using(r).get()
+				c2 = c1.take(3).reduce()
+
+				r.compose(key.t2, '1', c1)
+
+			then:
+				c2.awaitNext(1, TimeUnit.SECONDS)
+				c2.get() == [1, 100, 1000]
+		}*/
+
+	def "relay events to reactor"() {
+		given:
+			'a reactor and a selector'
+			def r = Reactors.reactor().env(testEnv).dispatcher('eventLoop').get()
+			def key = $()
+
+		when:
+			'we connect when this reactor and key'
+			def latch = new CountDownLatch(5)
+			r.on(key) {
+				latch.countDown()
+			}
+
+		and:
+			'Defer a composition'
+			Stream c = Streams.defer(['1', '2', '3', '4', '5']).get()
+
+		and:
+			'apply a transformation and call an explicit reactor'
+			def s = (c | { Integer.parseInt it }).to(key.object, r)
+			def t = s.tap()
+			s.flush()
+
+
+		then:
+			latch.await(1, TimeUnit.SECONDS)
+			latch.count == 0
+			t.get() == 5
+	}
+
+	def "compose from unknown number of values"() {
+
+		when:
+			'Defer a composition'
+			def c = Streams.defer(new TestIterable('1', '2', '3', '4', '5')).get()
+
+		and:
+			'apply a transformation and call an explicit reactor'
+			def sum = 0
+			Stream d = c | { Integer.parseInt it } | { sum += it; sum }
+
+		and:
+			'set a batch size to tap value after 5 iterations'
+			def t = d.last(5).tap()
+
+			d.flush()
+
+		then:
+			t.get()
+			sum == 15
+	}
+
+	static class TestIterable<T> implements Iterable<T> {
+
+		private final Collection<T> items;
+
+		public TestIterable(T... items) {
+			this.items = Arrays.asList(items);
+		}
+
+		@Override
+		public Iterator<T> iterator() {
+			return this.items.iterator();
+		}
+
+	}
+
+}
diff --git a/reactor-groovy/src/test/groovy/reactor/groovy/StaticConfiguration.groovy b/reactor-groovy/src/test/groovy/reactor/groovy/StaticConfiguration.groovy
new file mode 100644
index 0000000..b931bdc
--- /dev/null
+++ b/reactor-groovy/src/test/groovy/reactor/groovy/StaticConfiguration.groovy
@@ -0,0 +1,146 @@
+/* * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.groovy
+
+import groovy.transform.CompileStatic
+import reactor.core.configuration.DispatcherType
+import reactor.event.dispatch.SynchronousDispatcher
+import reactor.function.Consumer
+import reactor.function.Function
+import reactor.function.Predicate
+import reactor.groovy.config.GroovyEnvironment
+import reactor.event.Event
+import static reactor.event.selector.Selectors.*
+
+ at CompileStatic
+class StaticConfiguration {
+
+	static GroovyEnvironment test() {
+		GroovyEnvironment.create {
+			environment {
+				defaultDispatcher = "test"
+
+				dispatcher('test') {
+					type = DispatcherType.SYNCHRONOUS
+				}
+			}
+		}
+	}
+
+	static GroovyEnvironment test2() {
+		GroovyEnvironment.create {
+			reactor('test1') {
+				on('test') {
+				}
+
+				reactor('child_test1') {
+					ext 'a', 'rw'
+				}
+			}
+			reactor('test2') {
+				ext 'a', '2'
+			}
+		}
+	}
+
+	static GroovyEnvironment test3() {
+		GroovyEnvironment.create {
+			reactor('test1') {
+				dispatcher = new SynchronousDispatcher()
+				on('test') {
+					reply it
+				}
+
+			}
+		}
+	}
+
+	static GroovyEnvironment test4() {
+		def parentEnvironment = GroovyEnvironment.create {
+			environment {
+				defaultDispatcher = 'testDispatcher'
+
+				dispatcher 'testDispatcher', new SynchronousDispatcher()
+			}
+
+			reactor('test1') {
+				dispatcher 'testDispatcher'
+				routingStrategy 'random'
+				on('test') {
+					reply it
+				}
+			}
+		}
+
+		GroovyEnvironment.create {
+			include parentEnvironment
+
+			reactor('test1') {
+				on('test2') {
+					reply it
+				}
+			}
+			reactor('test2') {
+				dispatcher 'testDispatcher'
+			}
+		}
+	}
+
+	static GroovyEnvironment test5() {
+		GroovyEnvironment.create {
+			environment {
+				defaultDispatcher = 'testDispatcher'
+				dispatcher 'testDispatcher', new SynchronousDispatcher()
+			}
+
+			reactor('test1') {
+				stream{
+					map({ Event<?> ev->
+						ev.copy(ev.data.toString().startsWith('intercepted') ? ev.data : 'intercepted')
+					} as Function)
+				}
+				stream(object('test')){
+					map({ Event<?> ev->
+						ev.copy("$ev.data twice")
+					} as Function)
+				}
+				on('test') {
+					reply it
+				}
+				on('test2') {
+					reply it
+				}
+
+			}
+
+			reactor('test2') {
+				stream('test'){
+					filter({ Event<?> ev->
+						false
+					} as Predicate)
+				}
+				on('test') {
+					reply it
+					throw new Exception('never')
+				}
+				on('test2') {
+					reply it
+				}
+
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/reactor-logback/README.md b/reactor-logback/README.md
new file mode 100644
index 0000000..3f4805b
--- /dev/null
+++ b/reactor-logback/README.md
@@ -0,0 +1,52 @@
+# Disruptor-based AsyncAppender for Logback
+
+The `reactor-logback` module is somewhat misleadingly-named. There are no Reactor dependencies in it. It uses the LMAX Disruptor RingBuffer directly to provide high-speed, asynchronous logging for applications that don't want to give up logging just because it kills the performance of your async application.
+
+### Maven artifacts
+
+The Maven artifacts are currently in the snapshot repo. To use them, you'll need to add a reference to the snapshot repo in your build file. For Gradle, that would be something like:
+
+    ext {
+      reactorVersion = '1.0.0.BUILD-SNAPSHOT'
+    }
+
+    repositories {
+      mavenLocal()
+      mavenCentral()
+      maven { url 'http://repo.springsource.org/libs-snapshot' }
+    }
+
+    dependencies {
+      runtime "reactor:reactor-logback:$reactorVersion"
+    }
+
+### Configuration
+
+You use the AsyncAppender like any normal Logback appender. You can direct any logger to it and that logger's output will be written asynchronously. To configure Logback to do fully async logging for everything, just declare the AsyncAppender like the following (in `logback.xml`):
+
+    <configuration>
+
+      <!-- The underlying appender will be the standard console one. -->
+      <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+          <pattern>
+            %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+          </pattern>
+        </encoder>
+      </appender>
+
+      <!-- Wrap calls to the console logger with async dispatching to Disruptor. -->
+      <appender name="async" class="reactor.logback.AsyncAppender">
+        <appender-ref ref="stdout"/>
+      </appender>
+
+      <!-- Direct all logging through the AsyncAppender. -->
+      <root level="info">
+        <appender-ref ref="async"/>
+      </root>
+
+    </configuration>
+
+---
+
+Reactor is [Apache 2.0 licensed](http://www.apache.org/licenses/LICENSE-2.0.html).
diff --git a/reactor-logback/src/main/java/reactor/logback/AsyncAppender.java b/reactor-logback/src/main/java/reactor/logback/AsyncAppender.java
new file mode 100644
index 0000000..0d9adde
--- /dev/null
+++ b/reactor-logback/src/main/java/reactor/logback/AsyncAppender.java
@@ -0,0 +1,229 @@
+package reactor.logback;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.LogbackException;
+import ch.qos.logback.core.filter.Filter;
+import ch.qos.logback.core.spi.*;
+import reactor.core.processor.Operation;
+import reactor.core.processor.Processor;
+import reactor.core.processor.spec.ProcessorSpec;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A Logback {@literal Appender} implementation that uses a Reactor {@link reactor.core.processor.Processor} internally
+ * to queue events to a single-writer thread. This implementation doesn't do any actually appending itself, it just
+ * delegates to a "real" appender but it uses the efficient queueing mechanism of the {@literal RingBuffer} to do so.
+ *
+ * @author Jon Brisbin
+ */
+public class AsyncAppender
+		extends ContextAwareBase
+		implements Appender<ILoggingEvent>,
+		           AppenderAttachable<ILoggingEvent> {
+
+	private final AppenderAttachableImpl<ILoggingEvent>    aai      = new AppenderAttachableImpl<ILoggingEvent>();
+	private final FilterAttachableImpl<ILoggingEvent>      fai      = new FilterAttachableImpl<ILoggingEvent>();
+	private final AtomicReference<Appender<ILoggingEvent>> delegate = new AtomicReference<Appender<ILoggingEvent>>();
+
+	private String              name;
+	private Processor<LogEvent> processor;
+
+	private long    backlog           = 1024 * 1024;
+	private boolean includeCallerData = false;
+	private boolean started           = false;
+
+	public long getBacklog() {
+		return backlog;
+	}
+
+	public void setBacklog(long backlog) {
+		this.backlog = backlog;
+	}
+
+	public boolean isIncludeCallerData() {
+		return includeCallerData;
+	}
+
+	public void setIncludeCallerData(final boolean includeCallerData) {
+		this.includeCallerData = includeCallerData;
+	}
+
+	@Override
+	public String getName() {
+		return name;
+	}
+
+	@Override
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	@Override
+	public boolean isStarted() {
+		return started;
+	}
+
+	@Override
+	public void doAppend(ILoggingEvent evt) throws LogbackException {
+		if (getFilterChainDecision(evt) == FilterReply.DENY) {
+			return;
+		}
+		evt.prepareForDeferredProcessing();
+		if (includeCallerData) {
+			evt.getCallerData();
+		}
+		try {
+			queueLoggingEvent(evt);
+		} catch (Throwable t) {
+			addError(t.getMessage(), t);
+		}
+	}
+
+	@Override
+	public void start() {
+		if (null != delegate.get()) {
+			delegate.get().start();
+		}
+
+		processor = new ProcessorSpec<LogEvent>()
+				.dataSupplier(new Supplier<LogEvent>() {
+					@Override
+					public LogEvent get() {
+						return new LogEvent();
+					}
+				})
+				.multiThreadedProducer()
+				.dataBufferSize((int) backlog)
+				.when(Throwable.class, new Consumer<Throwable>() {
+					@Override
+					public void accept(Throwable throwable) {
+						addError(throwable.getMessage(), throwable);
+					}
+				})
+				.consume(new Consumer<LogEvent>() {
+					@Override
+					public void accept(LogEvent evt) {
+						loggingEventDequeued(evt.event);
+					}
+				})
+				.get();
+
+		try {
+			doStart();
+		} catch (Throwable t) {
+			addError(t.getMessage(), t);
+		} finally {
+			started = true;
+		}
+	}
+
+	@Override
+	public void stop() {
+		if (null != delegate.get()) {
+			delegate.get().stop();
+		}
+		aai.detachAndStopAllAppenders();
+
+		processor.shutdown();
+
+		try {
+			doStop();
+		} catch (Throwable t) {
+			addError(t.getMessage(), t);
+		} finally {
+			started = false;
+		}
+	}
+
+	@Override
+	public void addFilter(Filter<ILoggingEvent> newFilter) {
+		fai.addFilter(newFilter);
+	}
+
+	@Override
+	public void clearAllFilters() {
+		fai.clearAllFilters();
+	}
+
+	@Override
+	public List<Filter<ILoggingEvent>> getCopyOfAttachedFiltersList() {
+		return fai.getCopyOfAttachedFiltersList();
+	}
+
+	@Override
+	public FilterReply getFilterChainDecision(ILoggingEvent event) {
+		return fai.getFilterChainDecision(event);
+	}
+
+	@Override
+	public void addAppender(Appender<ILoggingEvent> newAppender) {
+		if (delegate.compareAndSet(null, newAppender)) {
+			aai.addAppender(newAppender);
+		} else {
+			throw new IllegalArgumentException(delegate.get() + " already attached.");
+		}
+	}
+
+	@Override
+	public Iterator<Appender<ILoggingEvent>> iteratorForAppenders() {
+		return aai.iteratorForAppenders();
+	}
+
+	@Override
+	public Appender<ILoggingEvent> getAppender(String name) {
+		return aai.getAppender(name);
+	}
+
+	@Override
+	public boolean isAttached(Appender<ILoggingEvent> appender) {
+		return aai.isAttached(appender);
+	}
+
+	@Override
+	public void detachAndStopAllAppenders() {
+		aai.detachAndStopAllAppenders();
+	}
+
+	@Override
+	public boolean detachAppender(Appender<ILoggingEvent> appender) {
+		return aai.detachAppender(appender);
+	}
+
+	@Override
+	public boolean detachAppender(String name) {
+		return aai.detachAppender(name);
+	}
+
+	protected AppenderAttachableImpl<ILoggingEvent> getAppenderImpl() {
+		return aai;
+	}
+
+	protected void doStart() {
+	}
+
+	protected void doStop() {
+	}
+
+	protected void queueLoggingEvent(ILoggingEvent evt) {
+		if (null != delegate.get()) {
+			Operation<LogEvent> op = processor.prepare();
+			op.get().event = evt;
+			op.commit();
+		}
+	}
+
+	protected void loggingEventDequeued(ILoggingEvent evt) {
+		aai.appendLoopOnAppenders(evt);
+	}
+
+	private static class LogEvent {
+		ILoggingEvent event;
+	}
+
+}
diff --git a/reactor-logback/src/main/java/reactor/logback/DurableAsyncAppender.java b/reactor-logback/src/main/java/reactor/logback/DurableAsyncAppender.java
new file mode 100644
index 0000000..0db0df2
--- /dev/null
+++ b/reactor-logback/src/main/java/reactor/logback/DurableAsyncAppender.java
@@ -0,0 +1,72 @@
+package reactor.logback;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import net.openhft.chronicle.Chronicle;
+import net.openhft.chronicle.ChronicleConfig;
+import net.openhft.chronicle.ExcerptAppender;
+import net.openhft.chronicle.IndexedChronicle;
+import net.openhft.chronicle.tools.ChronicleTools;
+
+import java.io.IOException;
+
+/**
+ * An {@literal AsyncAppender} subclass that first writes a log event to a durable {@literal Chronicle} using Java
+ * Chronicle before allowing the event to be queued.
+ *
+ * @author Jon Brisbin
+ */
+public class DurableAsyncAppender extends AsyncAppender {
+
+	private final Object writeMonitor = new Object();
+
+	private String basePath = "log";
+
+	private Chronicle       chronicle;
+	private ExcerptAppender appender;
+
+	public DurableAsyncAppender() {
+	}
+
+	public String getBasePath() {
+		return basePath;
+	}
+
+	public void setBasePath(String chronicle) {
+		this.basePath = chronicle;
+	}
+
+	@Override
+	protected void doStart() {
+		ChronicleTools.warmup();
+		ChronicleConfig config = ChronicleConfig.DEFAULT.clone()
+		                                                .synchronousMode(false)
+		                                                .useUnsafe(true);
+		this.basePath = (this.basePath.endsWith("/") ? this.basePath + getName() : this.basePath + "/" + getName());
+		try {
+			chronicle = new IndexedChronicle(basePath, config);
+			appender = chronicle.createAppender();
+		} catch (Throwable t) {
+			addError(t.getMessage(), t);
+		}
+	}
+
+	@Override
+	protected void doStop() {
+		try {
+			appender.flush();
+			chronicle.close();
+		} catch (IOException e) {
+			addError(e.getMessage(), e);
+		}
+	}
+
+	@Override
+	protected void queueLoggingEvent(ILoggingEvent evt) {
+		synchronized (writeMonitor) {
+			LoggingEventRecord.write(appender, (LoggingEvent) evt, isIncludeCallerData(), 1);
+		}
+		super.queueLoggingEvent(evt);
+	}
+
+}
diff --git a/reactor-logback/src/main/java/reactor/logback/DurableLogUtility.java b/reactor-logback/src/main/java/reactor/logback/DurableLogUtility.java
new file mode 100644
index 0000000..17d4ecf
--- /dev/null
+++ b/reactor-logback/src/main/java/reactor/logback/DurableLogUtility.java
@@ -0,0 +1,175 @@
+package reactor.logback;
+
+import ch.qos.logback.classic.BasicConfigurator;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableProxy;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.StatusPrinter;
+import net.openhft.chronicle.Chronicle;
+import net.openhft.chronicle.ChronicleConfig;
+import net.openhft.chronicle.ExcerptTailer;
+import net.openhft.chronicle.IndexedChronicle;
+import net.openhft.chronicle.tools.ChronicleTools;
+import org.apache.commons.cli.*;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.regex.Pattern;
+
+/**
+ * @author Jon Brisbin
+ */
+public class DurableLogUtility {
+
+	private static Options OPTS = new Options();
+
+	static {
+		Option path = new Option("p", "path", true, "Base path to a durable log file to interpret");
+		path.setRequired(true);
+
+		Option config = new Option("c", "config", true, "Logback configuration XML file to parse");
+		Option output = new Option("o", "output", true, "Appender to use to output results");
+
+		Option regex = new Option("search", true, "Search for the given regex and print to standard out");
+		Option level = new Option("level", true, "Log level to filter");
+		Option head = new Option("head", true, "Number of lines to display from the head of the file");
+		Option tail = new Option("tail", true, "Number of lines to display from the tail of the file");
+		OptionGroup findOpts = new OptionGroup()
+				.addOption(regex)
+				.addOption(head)
+				.addOption(tail);
+
+		OPTS.addOption(path)
+		    .addOption(config)
+		    .addOption(output)
+		    .addOption(level)
+		    .addOptionGroup(findOpts);
+	}
+
+	@SuppressWarnings("unchecked")
+	public static void main(String... args) throws ParseException,
+	                                               JoranException,
+	                                               IOException {
+		Parser parser = new BasicParser();
+		CommandLine cl = null;
+		try {
+			cl = parser.parse(OPTS, args);
+		} catch (ParseException e) {
+			HelpFormatter help = new HelpFormatter();
+			help.printHelp("dlog", OPTS, true);
+			System.exit(-1);
+		}
+
+		LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+		loggerContext.reset();
+
+		if (cl.hasOption("config")) {
+			// Read Logback configuration
+			JoranConfigurator configurator = new JoranConfigurator();
+			configurator.setContext(loggerContext);
+
+			configurator.doConfigure(cl.getOptionValue("file", "logback.xml"));
+
+			StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);
+		} else {
+			BasicConfigurator.configure(loggerContext);
+		}
+
+		Appender appender = null;
+		if (cl.hasOption("output")) {
+			String outputAppender = cl.getOptionValue("output", "console");
+			appender = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).getAppender(outputAppender);
+		}
+
+		ChronicleTools.warmup();
+		Chronicle chronicle = new IndexedChronicle(cl.getOptionValue("path"), ChronicleConfig.DEFAULT);
+		ExcerptTailer ex = chronicle.createTailer();
+
+		Level level = Level.valueOf(cl.getOptionValue("level", "TRACE"));
+
+		if (cl.hasOption("head")) {
+			int lines = Integer.parseInt(cl.getOptionValue("head", "10"));
+			for (int i = 0; i < lines; i++) {
+				LoggingEvent evt = readLoggingEvent(ex, loggerContext);
+				if (evt.getLevel().isGreaterOrEqual(level)) {
+					writeEvent(evt, appender);
+				}
+			}
+		} else if (cl.hasOption("tail")) {
+			int lines = Integer.parseInt(cl.getOptionValue("tail", "10"));
+			Queue<LoggingEvent> tail = new LinkedBlockingQueue<LoggingEvent>(lines);
+			while (ex.nextIndex()) {
+				LoggingEvent evt = readLoggingEvent(ex, loggerContext);
+				if (!tail.offer(evt)) {
+					tail.poll();
+					tail.add(evt);
+				}
+			}
+			LoggingEvent evt;
+			while (null != (evt = tail.poll())) {
+				if (evt.getLevel().isGreaterOrEqual(level)) {
+					writeEvent(evt, appender);
+				}
+			}
+		} else if (cl.hasOption("search")) {
+			String regex = cl.getOptionValue("search");
+			Pattern regexPatt = Pattern.compile(regex);
+			while (ex.nextIndex()) {
+				LoggingEvent evt = readLoggingEvent(ex, loggerContext);
+				if (null != evt && evt.getLevel().isGreaterOrEqual(level)) {
+					if (regexPatt.matcher(evt.getFormattedMessage()).matches()) {
+						writeEvent(evt, appender);
+					}
+				}
+			}
+		}
+
+		loggerContext.stop();
+		chronicle.close();
+	}
+
+	@SuppressWarnings("unchecked")
+	private static void writeEvent(LoggingEvent evt, Appender appender) {
+		if (null == evt) {
+			return;
+		}
+		if (null != appender) {
+			appender.doAppend(evt);
+		} else {
+			System.out.println(evt.getFormattedMessage());
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private static LoggingEvent readLoggingEvent(ExcerptTailer ex,
+	                                             LoggerContext ctx) {
+		LoggingEventRecord rec = LoggingEventRecord.read(ex);
+
+		Logger logger = ctx.getLogger(rec.getLoggerName());
+		LoggingEvent evt = new LoggingEvent(
+				logger.getClass().getName(),
+				logger,
+				Level.toLevel(rec.getLevel()),
+				rec.getMessage(),
+				rec.getCause(),
+				rec.getArgs()
+		);
+		evt.setTimeStamp(rec.getTimestamp());
+		evt.setThreadName(rec.getThreadName());
+		evt.setMDCPropertyMap(rec.getMdcProps());
+		if (null != rec.getCause()) {
+			evt.setThrowableProxy(new ThrowableProxy(rec.getCause()));
+		}
+		evt.setCallerData(rec.getCallerData());
+
+		return evt;
+	}
+
+}
diff --git a/reactor-logback/src/main/java/reactor/logback/LoggingEventRecord.java b/reactor-logback/src/main/java/reactor/logback/LoggingEventRecord.java
new file mode 100644
index 0000000..4c0c891
--- /dev/null
+++ b/reactor-logback/src/main/java/reactor/logback/LoggingEventRecord.java
@@ -0,0 +1,166 @@
+package reactor.logback;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+import net.openhft.chronicle.ExcerptAppender;
+import net.openhft.chronicle.ExcerptTailer;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Jon Brisbin
+ */
+class LoggingEventRecord implements Serializable {
+
+	private static final long   serialVersionUID = 4286033251454846145L;
+	private static final String CRLF             = "\r\n";
+
+	private long                timestamp;
+	private String              threadName;
+	private String              loggerName;
+	private int                 level;
+	private String              message;
+	private Object[]            args;
+	private StackTraceElement[] callerData;
+	private Map<String, String> mdcProps;
+	private Throwable           cause;
+
+	public LoggingEventRecord() {
+	}
+
+	public long getTimestamp() {
+		return timestamp;
+	}
+
+	public String getThreadName() {
+		return threadName;
+	}
+
+	public String getLoggerName() {
+		return loggerName;
+	}
+
+	public int getLevel() {
+		return level;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public Object[] getArgs() {
+		return args;
+	}
+
+	public StackTraceElement[] getCallerData() {
+		return callerData;
+	}
+
+	public Map<String, String> getMdcProps() {
+		return mdcProps;
+	}
+
+	public Throwable getCause() {
+		return cause;
+	}
+
+	static void write(ExcerptAppender ex, LoggingEvent evt, boolean includeCallerData, int vers) {
+		ex.startExcerpt(32 * 1024);
+		ex.writeInt(vers);
+
+		if (vers == 1) {
+			ex.writeLong(evt.getTimeStamp());
+			ex.writeInt(evt.getLevel().toInt());
+
+			Object[] args = evt.getArgumentArray();
+			int argLen = (null != args ? args.length : 0);
+			ex.writeInt(argLen);
+
+			Map<String, String> mdcProps = evt.getMDCPropertyMap();
+			int propsLen = (null != mdcProps ? mdcProps.size() : 0);
+			ex.writeInt(propsLen);
+
+			StackTraceElement[] callerData = null;
+			if (includeCallerData) {
+				callerData = evt.getCallerData();
+			}
+			int callerDataLen = (null != callerData ? callerData.length : 0);
+			ex.writeInt(callerDataLen);
+
+			ex.writeUTF(evt.getThreadName());
+			ex.writeUTF(evt.getLoggerName());
+			ex.writeUTF(evt.getMessage());
+
+			for (int i = 0; i < argLen; i++) {
+				ex.writeUTF(args[i].toString());
+			}
+
+			for (Map.Entry<String, String> entry : evt.getMDCPropertyMap().entrySet()) {
+				ex.writeUTF(entry.getKey());
+				ex.writeUTF(entry.getValue());
+			}
+
+			for (int i = 0; i < callerDataLen; i++) {
+				ex.writeObject(callerData[i]);
+			}
+
+			boolean hasCause = null != evt.getThrowableProxy();
+			ex.writeBoolean(hasCause);
+			if (hasCause) {
+				ex.writeObject((Throwable) evt.getThrowableProxy());
+			}
+		}
+
+		ex.finish();
+	}
+
+	@SuppressWarnings("unchecked")
+	static LoggingEventRecord read(ExcerptTailer ex) {
+		int vers = ex.readInt();
+		if (vers == 1) {
+			LoggingEventRecord rec = new LoggingEventRecord();
+
+			rec.timestamp = ex.readLong();
+			rec.level = ex.readInt();
+			int argLen = ex.readInt();
+			int propsLen = ex.readInt();
+			int callerDataLen = ex.readInt();
+
+			rec.threadName = ex.readUTF();
+			rec.loggerName = ex.readUTF();
+			rec.message = ex.readUTF();
+
+			String[] args = new String[argLen];
+			for (int i = 0; i < argLen; i++) {
+				args[i] = ex.readUTF();
+			}
+			rec.args = args;
+
+			Map<String, String> mdcProps = (propsLen > 0
+					? new HashMap<String, String>()
+					: Collections.<String, String>emptyMap());
+			for (int i = 0; i < propsLen; i++) {
+				String key = ex.readUTF();
+				String val = ex.readUTF();
+				mdcProps.put(key, val);
+			}
+			rec.mdcProps = mdcProps;
+
+			StackTraceElement[] callerData = new StackTraceElement[callerDataLen];
+			for (int i = 0; i < callerDataLen; i++) {
+				callerData[i] = ex.readObject(StackTraceElement.class);
+			}
+			rec.callerData = callerData;
+
+			if (ex.readBoolean()) {
+				rec.cause = ex.readObject(Throwable.class);
+			}
+
+			return rec;
+		}
+		throw new IllegalStateException("Version " + vers + " not supported");
+	}
+
+}
diff --git a/reactor-logback/src/test/java/reactor/dummy/Test.java b/reactor-logback/src/test/java/reactor/dummy/Test.java
new file mode 100644
index 0000000..c3f2f19
--- /dev/null
+++ b/reactor-logback/src/test/java/reactor/dummy/Test.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.dummy;
+
+/**
+ * Only used for log configuration
+ *
+ * @author Stephane Maldini
+ */
+public class Test {
+}
diff --git a/reactor-logback/src/test/java/reactor/logback/AsyncAppenderTests.java b/reactor-logback/src/test/java/reactor/logback/AsyncAppenderTests.java
new file mode 100644
index 0000000..1197d44
--- /dev/null
+++ b/reactor-logback/src/test/java/reactor/logback/AsyncAppenderTests.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.logback;
+
+import ch.qos.logback.classic.Logger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+import reactor.support.NamedDaemonThreadFactory;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author Jon Brisbin
+ */
+public class AsyncAppenderTests {
+
+	static final String MSG;
+
+	static {
+		String ABCS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+		Random r = new Random();
+
+		char[] chars = new char[20000];
+		int len = chars.length;
+		for (int i = 0; i < len; i++) {
+			chars[i] = ABCS.charAt(r.nextInt(ABCS.length()));
+		}
+		MSG = new String(chars);
+	}
+
+	final int timeout = 1;
+	ExecutorService threadPool;
+	Logger          syncLog;
+	Logger          asyncLog;
+	Logger          chronicleLog;
+
+	@Before
+	public void setup() throws IOException {
+		Path logDir = Paths.get("log");
+		if (Files.exists(logDir)) {
+			Files.find(logDir, 1, (pth, attrs) -> pth.toString().endsWith(".log"))
+			     .forEach(pth -> {
+				     try {
+					     Files.delete(pth);
+				     } catch (IOException e) {
+					     throw new IllegalArgumentException(e.getMessage(), e);
+				     }
+			     });
+		}
+
+		threadPool = Executors.newCachedThreadPool(new NamedDaemonThreadFactory("benchmark-writers"));
+		syncLog = (Logger) LoggerFactory.getLogger("sync");
+		asyncLog = (Logger) LoggerFactory.getLogger("async");
+		chronicleLog = (Logger) LoggerFactory.getLogger("chronicle");
+	}
+
+	@After
+	public void cleanup() {
+		threadPool.shutdownNow();
+		chronicleLog.getLoggerContext().stop();
+	}
+
+	//@Test
+	public void clockSyncAppender() throws InterruptedException {
+		long m = benchmarkThread(syncLog, timeout);
+		System.out.println("sync: " + (m / timeout) + "/sec");
+	}
+
+	//@Test
+	public void clockAsyncAppender() throws InterruptedException {
+		long n = benchmarkThread(asyncLog, timeout);
+		System.out.println("async: " + (n / timeout) + "/sec");
+	}
+
+	//@Test
+	public void clockChronicleAppender() throws InterruptedException {
+		long n = benchmarkThread(chronicleLog, timeout);
+		System.out.println("chronicle: " + (n / timeout) + "/sec");
+	}
+
+	//@Test
+	public void clockAllAppenders() throws InterruptedException {
+		clockSyncAppender();
+		clockAsyncAppender();
+		clockChronicleAppender();
+	}
+
+	@Test
+	public void dummy() {
+	}
+
+	private long benchmarkThread(final Logger logger, int timeout) throws InterruptedException {
+		final CountDownLatch latch = new CountDownLatch(1);
+		final AtomicLong throughput = new AtomicLong(0);
+
+		int threads = Runtime.getRuntime().availableProcessors() * 4;
+		for (int i = 0; i < threads; i++) {
+			threadPool.submit(new Runnable() {
+				@Override
+				public void run() {
+					while (latch.getCount() > 0) {
+						logger.warn("count: {}", throughput.incrementAndGet());
+					}
+				}
+			});
+		}
+		latch.await(timeout, TimeUnit.SECONDS);
+		latch.countDown();
+
+		return throughput.get();
+	}
+
+}
diff --git a/reactor-logback/src/test/resources/logback.xml b/reactor-logback/src/test/resources/logback.xml
new file mode 100644
index 0000000..7e11c83
--- /dev/null
+++ b/reactor-logback/src/test/resources/logback.xml
@@ -0,0 +1,72 @@
+<!--
+  ~ Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~       http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration debug="true">
+
+	<appender name="syncFile" class="ch.qos.logback.core.FileAppender">
+		<file>log/sync.log</file>
+		<encoder>
+			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<appender name="asyncFile" class="ch.qos.logback.core.FileAppender">
+		<file>log/async.log</file>
+		<encoder>
+			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<appender name="chronicleFile" class="ch.qos.logback.core.FileAppender">
+		<file>log/chronicle.log</file>
+		<encoder>
+			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<appender name="async" class="reactor.logback.AsyncAppender">
+		<appender-ref ref="asyncFile"/>
+	</appender>
+
+	<!--<appender name="async" class="ch.qos.logback.classic.AsyncAppender">-->
+		<!--<appender-ref ref="asyncFile"/>-->
+	<!--</appender>-->
+
+	<appender name="chronicle" class="reactor.logback.DurableAsyncAppender">
+		<appender-ref ref="chronicleFile"/>
+		<basePath>log/</basePath>
+		<backlog>2097152</backlog>
+	</appender>
+
+	<logger name="sync" level="warn">
+		<appender-ref ref="syncFile"/>
+	</logger>
+
+	<logger name="async" level="warn">
+		<appender-ref ref="async"/>
+	</logger>
+
+	<logger name="chronicle" level="warn">
+		<appender-ref ref="chronicle"/>
+	</logger>
+
+</configuration>
\ No newline at end of file
diff --git a/reactor-net/src/main/java/reactor/net/AbstractNetChannel.java b/reactor-net/src/main/java/reactor/net/AbstractNetChannel.java
new file mode 100644
index 0000000..a4d9869
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/AbstractNetChannel.java
@@ -0,0 +1,326 @@
+package reactor.net;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.core.composable.spec.Promises;
+import reactor.core.support.NotifyConsumer;
+import reactor.event.Event;
+import reactor.event.dispatch.Dispatcher;
+import reactor.event.registry.Registration;
+import reactor.event.selector.Selector;
+import reactor.event.selector.Selectors;
+import reactor.event.support.EventConsumer;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.function.batch.BatchConsumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.queue.BlockingQueueFactory;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.nio.ByteBuffer;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+
+import static reactor.event.selector.Selectors.$;
+
+/**
+ * An abstract {@link reactor.net.NetChannel} implementation that handles the basic interaction and {@link
+ * reactor.core.composable.Stream} and {@link reactor.function.Consumer} handling.
+ *
+ * @author Jon Brisbin
+ */
+public abstract class AbstractNetChannel<IN, OUT> implements NetChannel<IN, OUT> {
+
+	protected final Logger   log  = LoggerFactory.getLogger(getClass());
+	private final   Selector read = $();
+
+	private final Environment            env;
+	private final Reactor                ioReactor;
+	private final Reactor                eventsReactor;
+	private final Codec<Buffer, IN, OUT> codec;
+	private final Function<Buffer, IN>   decoder;
+	private final Function<OUT, Buffer>  encoder;
+	private final Queue<Object>          replyToKeys;
+
+	protected AbstractNetChannel(@Nonnull Environment env,
+	                             @Nullable Codec<Buffer, IN, OUT> codec,
+	                             @Nonnull Dispatcher ioDispatcher,
+	                             @Nonnull Reactor eventsReactor) {
+		Assert.notNull(env, "IO Dispatcher cannot be null");
+		Assert.notNull(env, "Events Reactor cannot be null");
+		this.env = env;
+		this.ioReactor = new Reactor(ioDispatcher,
+		                             null,
+		                             eventsReactor.getDispatchErrorHandler(),
+		                             eventsReactor.getUncaughtErrorHandler());
+		this.eventsReactor = new Reactor(eventsReactor.getDispatcher(),
+		                                 null,
+		                                 eventsReactor.getDispatchErrorHandler(),
+		                                 eventsReactor.getUncaughtErrorHandler());
+		this.eventsReactor.getConsumerRegistry().clear();
+		for (Registration<? extends Consumer<? extends Event<?>>> reg : eventsReactor.getConsumerRegistry()) {
+			this.eventsReactor.getConsumerRegistry().register(reg.getSelector(), reg.getObject());
+		}
+		this.codec = codec;
+		if (null != codec) {
+			this.decoder = codec.decoder(new NotifyConsumer<IN>(read.getObject(), this.eventsReactor));
+			this.encoder = codec.encoder();
+		} else {
+			this.decoder = null;
+			this.encoder = null;
+		}
+		this.replyToKeys = BlockingQueueFactory.createQueue();
+
+		consume(new Consumer<IN>() {
+			@Override
+			public void accept(IN in) {
+				try {
+					if (!replyToKeys.isEmpty()) {
+						AbstractNetChannel.this.eventsReactor.notify(replyToKeys.remove(), Event.wrap(in));
+					}
+				} catch (NoSuchElementException ignored) {
+				}
+			}
+		});
+	}
+
+	public Function<Buffer, IN> getDecoder() {
+		return decoder;
+	}
+
+	public Function<OUT, Buffer> getEncoder() {
+		return encoder;
+	}
+
+	@Override
+	public Stream<IN> in() {
+		final Deferred<IN, Stream<IN>> d = new Deferred<IN, Stream<IN>>(new Stream<IN>(eventsReactor, -1, null, env));
+		consume(new Consumer<IN>() {
+			@Override
+			public void accept(IN in) {
+				d.accept(in);
+			}
+		});
+		return d.compose();
+	}
+
+	@Override
+	public BatchConsumer<OUT> out() {
+		return new WriteConsumer(null);
+	}
+
+	@Override
+	public <T extends Throwable> NetChannel<IN, OUT> when(Class<T> errorType, Consumer<T> errorConsumer) {
+		eventsReactor.on(Selectors.T(errorType), new EventConsumer<T>(errorConsumer));
+		return this;
+	}
+
+	@Override
+	public NetChannel<IN, OUT> consume(final Consumer<IN> consumer) {
+		eventsReactor.on(read, new Consumer<Event<IN>>() {
+			@Override
+			public void accept(Event<IN> ev) {
+				consumer.accept(ev.getData());
+			}
+		});
+		return this;
+	}
+
+	@Override
+	public NetChannel<IN, OUT> receive(final Function<IN, OUT> fn) {
+		consume(new Consumer<IN>() {
+			@Override
+			public void accept(IN in) {
+				send(fn.apply(in));
+			}
+		});
+		return this;
+	}
+
+	@Override
+	public NetChannel<IN, OUT> send(Stream<OUT> data) {
+		data.consume(new Consumer<OUT>() {
+			@Override
+			public void accept(OUT out) {
+				send(out, null);
+			}
+		});
+		return this;
+	}
+
+	@Override
+	public Promise<Void> send(OUT data) {
+		Deferred<Void, Promise<Void>> d = Promises.defer(env, eventsReactor.getDispatcher());
+		send(data, d);
+		return d.compose();
+	}
+
+	@Override
+	public NetChannel<IN, OUT> sendAndForget(OUT data) {
+		send(data, null);
+		return this;
+	}
+
+	@Override
+	public Promise<IN> sendAndReceive(OUT data) {
+		final Deferred<IN, Promise<IN>> d = Promises.defer(env, eventsReactor.getDispatcher());
+		Selector sel = $();
+		eventsReactor.on(sel, new EventConsumer<IN>(d)).cancelAfterUse();
+		replyToKeys.add(sel.getObject());
+		send(data, null);
+		return d.compose();
+	}
+
+	@Override
+	public Promise<Boolean> close() {
+		Deferred<Boolean, Promise<Boolean>> d = Promises.defer(getEnvironment(), eventsReactor.getDispatcher());
+		eventsReactor.getConsumerRegistry().unregister(read.getObject());
+		close(d);
+		return d.compose();
+	}
+
+	/**
+	 * Send data on this connection. The current codec (if any) will be used to encode the data to a {@link
+	 * reactor.io.Buffer}. The given callback will be invoked when the write has completed.
+	 *
+	 * @param data
+	 * 		The outgoing data.
+	 * @param onComplete
+	 * 		The callback to invoke when the write is complete.
+	 */
+	protected void send(OUT data, final Deferred<Void, Promise<Void>> onComplete) {
+		ioReactor.schedule(new WriteConsumer(onComplete), data);
+	}
+
+	/**
+	 * Performing necessary decoding on the data and notify the internal {@link Reactor} of any results.
+	 *
+	 * @param data
+	 * 		The data to decode.
+	 *
+	 * @return {@literal true} if any more data is remaining to be consumed in the given {@link Buffer}, {@literal false}
+	 * otherwise.
+	 */
+	public boolean read(Buffer data) {
+		if (null != decoder && null != data.byteBuffer()) {
+			decoder.apply(data);
+		} else {
+			eventsReactor.notify(read.getObject(), Event.wrap(data));
+		}
+
+		return data.remaining() > 0;
+	}
+
+	public void notifyRead(Object obj) {
+		eventsReactor.notify(read.getObject(), (Event.class.isInstance(obj) ? (Event) obj : Event.wrap(obj)));
+	}
+
+	public void notifyError(Throwable throwable) {
+		eventsReactor.notify(throwable.getClass(), Event.wrap(throwable));
+	}
+
+	/**
+	 * Subclasses must implement this method to perform the actual IO of writing data to the connection.
+	 *
+	 * @param data
+	 * 		The data to write, as a {@link Buffer}.
+	 * @param onComplete
+	 * 		The callback to invoke when the write is complete.
+	 */
+	protected void write(Buffer data, Deferred<Void, Promise<Void>> onComplete, boolean flush) {
+		write(data.byteBuffer(), onComplete, flush);
+	}
+
+	/**
+	 * Subclasses must implement this method to perform the actual IO of writing data to the connection.
+	 *
+	 * @param data
+	 * 		The data to write.
+	 * @param onComplete
+	 * 		The callback to invoke when the write is complete.
+	 * @param flush
+	 * 		whether to flush the underlying IO channel
+	 */
+	protected abstract void write(ByteBuffer data, Deferred<Void, Promise<Void>> onComplete, boolean flush);
+
+	/**
+	 * Subclasses must implement this method to perform the actual IO of writing data to the connection.
+	 *
+	 * @param data
+	 * 		The data to write.
+	 * @param onComplete
+	 * 		The callback to invoke when the write is complete.
+	 * @param flush
+	 * 		whether to flush the underlying IO channel
+	 */
+	protected abstract void write(Object data, Deferred<Void, Promise<Void>> onComplete, boolean flush);
+
+	/**
+	 * Subclasses must implement this method to perform IO flushes.
+	 */
+	protected abstract void flush();
+
+	protected Environment getEnvironment() {
+		return env;
+	}
+
+	protected Reactor getEventsReactor() {
+		return eventsReactor;
+	}
+
+	protected Reactor getIoReactor() {
+		return ioReactor;
+	}
+
+	private final class WriteConsumer implements BatchConsumer<OUT> {
+		private final Deferred<Void, Promise<Void>> onComplete;
+		private volatile boolean autoflush = true;
+
+		private WriteConsumer(Deferred<Void, Promise<Void>> onComplete) {
+			this.onComplete = onComplete;
+		}
+
+		@Override
+		public void start() {
+			autoflush = false;
+		}
+
+		@Override
+		public void end() {
+			flush();
+			autoflush = true;
+		}
+
+		@Override
+		public void accept(OUT data) {
+			try {
+				if (null != encoder) {
+					Buffer bytes = encoder.apply(data);
+					if (bytes.remaining() > 0) {
+						write(bytes, onComplete, autoflush);
+					}
+				} else {
+					if (Buffer.class.isInstance(data)) {
+						write((Buffer) data, onComplete, autoflush);
+					} else {
+						write(data, onComplete, autoflush);
+					}
+				}
+			} catch (Throwable t) {
+				eventsReactor.notify(t.getClass(), Event.wrap(t));
+				if (null != onComplete) {
+					onComplete.accept(t);
+				}
+			}
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/AbstractNetPeer.java b/reactor-net/src/main/java/reactor/net/AbstractNetPeer.java
new file mode 100644
index 0000000..58d9878
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/AbstractNetPeer.java
@@ -0,0 +1,260 @@
+package reactor.net;
+
+import com.gs.collections.impl.list.mutable.FastList;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.spec.Promises;
+import reactor.event.Event;
+import reactor.event.registry.CachingRegistry;
+import reactor.event.registry.Registration;
+import reactor.event.registry.Registry;
+import reactor.event.selector.Selector;
+import reactor.event.selector.Selectors;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Abstract base class that implements common functionality shared by clients and servers.
+ *
+ * @author Jon Brisbin
+ */
+public abstract class AbstractNetPeer<IN, OUT> {
+
+	private final Registry<NetChannel<IN, OUT>>   netChannels = new CachingRegistry<NetChannel<IN, OUT>>();
+	private final Event<AbstractNetPeer<IN, OUT>> selfEvent   = Event.wrap(this);
+	private final Selector                        open        = Selectors.$();
+	private final Selector                        close       = Selectors.$();
+	private final Selector                        start       = Selectors.$();
+	private final Selector                        shutdown    = Selectors.$();
+
+	private final Environment                               env;
+	private final Reactor                                   reactor;
+	private final Codec<Buffer, IN, OUT>                    codec;
+	private final Collection<Consumer<NetChannel<IN, OUT>>> consumers;
+
+	protected AbstractNetPeer(@Nonnull Environment env,
+	                          @Nonnull Reactor reactor,
+	                          @Nullable Codec<Buffer, IN, OUT> codec,
+	                          @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		this.env = env;
+		this.reactor = reactor;
+		this.codec = codec;
+		this.consumers = consumers;
+
+		for (final Consumer<NetChannel<IN, OUT>> consumer : consumers) {
+			reactor.on(open, new Consumer<Event<NetChannel<IN, OUT>>>() {
+				@Override
+				public void accept(Event<NetChannel<IN, OUT>> ev) {
+					consumer.accept(ev.getData());
+				}
+			});
+		}
+	}
+
+	public Promise<Boolean> close() {
+		Deferred<Boolean, Promise<Boolean>> d = Promises.defer(env, reactor.getDispatcher());
+		close(d);
+		return d.compose();
+	}
+
+	public void close(@Nullable final Consumer<Boolean> onClose) {
+		for (Registration<? extends NetChannel<IN, OUT>> reg : getChannels()) {
+			if (!reg.isCancelled()) {
+				doCloseChannel(reg.getObject());
+			}
+		}
+		if (null != onClose) {
+			reactor.schedule(onClose, true);
+		}
+	}
+
+	public Iterator<NetChannel<IN, OUT>> iterator() {
+		FastList<NetChannel<IN, OUT>> channels = FastList.newList();
+		for (Registration<? extends NetChannel<IN, OUT>> reg : getChannels()) {
+			channels.add(reg.getObject());
+		}
+		return channels.iterator();
+	}
+
+	/**
+	 * Subclasses should register the given {@link reactor.net.NetChannel} for later use.
+	 *
+	 * @param ioChannel
+	 * 		The channel object.
+	 * @param netChannel
+	 * 		The {@link NetChannel}.
+	 * @param <C>
+	 * 		The type of the channel object.
+	 *
+	 * @return {@link reactor.event.registry.Registration} of this channel in the {@link Registry}.
+	 */
+	protected <C> Registration<? extends NetChannel<IN, OUT>> register(@Nonnull C ioChannel,
+	                                                                   @Nonnull NetChannel<IN, OUT> netChannel) {
+		Assert.notNull(ioChannel, "Channel cannot be null.");
+		Assert.notNull(netChannel, "NetChannel cannot be null.");
+		return netChannels.register(Selectors.$(ioChannel), netChannel);
+	}
+
+	/**
+	 * Find the {@link NetChannel} for the given IO channel object.
+	 *
+	 * @param ioChannel
+	 * 		The channel object.
+	 * @param <C>
+	 * 		The type of the channel object.
+	 *
+	 * @return The {@link NetChannel} associated with the given channel.
+	 */
+	protected <C> NetChannel<IN, OUT> select(@Nonnull C ioChannel) {
+		Assert.notNull(ioChannel, "Channel cannot be null.");
+		Iterator<Registration<? extends NetChannel<IN, OUT>>> channs = netChannels.select(ioChannel).iterator();
+		if (channs.hasNext()) {
+			return channs.next().getObject();
+		} else {
+			NetChannel<IN, OUT> conn = createChannel(ioChannel);
+			register(ioChannel, conn);
+			notifyOpen(conn);
+			return conn;
+		}
+	}
+
+	/**
+	 * Close the given channel.
+	 *
+	 * @param channel
+	 * 		The channel object.
+	 * @param <C>
+	 * 		The type of the channel object.
+	 */
+	protected <C> void close(@Nonnull C channel) {
+		Assert.notNull(channel, "Channel cannot be null");
+		for (Registration<? extends NetChannel<IN, OUT>> reg : netChannels.select(channel)) {
+			NetChannel<IN, OUT> chann = reg.getObject();
+			reg.cancel();
+			notifyClose(chann);
+		}
+	}
+
+	/**
+	 * Subclasses should implement this method and provide a {@link NetChannel} object.
+	 *
+	 * @param ioChannel
+	 * 		The IO channel object to associate with this {@link reactor.net.NetChannel}.
+	 * @param <C>
+	 * 		The type of the channel object.
+	 *
+	 * @return The new {@link NetChannel} object.
+	 */
+	protected abstract <C> NetChannel<IN, OUT> createChannel(C ioChannel);
+
+	/**
+	 * Notify this server's consumers that the server has started.
+	 */
+	protected void notifyStart(final Runnable started) {
+		getReactor().notify(start.getObject(), selfEvent);
+		if (null != started) {
+			getReactor().schedule(new Consumer<Runnable>() {
+				@Override
+				public void accept(Runnable r) {
+					r.run();
+				}
+			}, started);
+		}
+	}
+
+	/**
+	 * Notify this client's consumers than a global error has occurred.
+	 *
+	 * @param error
+	 * 		The error to notify.
+	 */
+	protected void notifyError(@Nonnull Throwable error) {
+		Assert.notNull(error, "Error cannot be null.");
+		reactor.notify(error.getClass(), Event.wrap(error));
+	}
+
+	/**
+	 * Notify this peer's consumers that the channel has been opened.
+	 *
+	 * @param channel
+	 * 		The channel that was opened.
+	 */
+	protected void notifyOpen(@Nonnull NetChannel<IN, OUT> channel) {
+		reactor.notify(open.getObject(), Event.wrap(channel));
+	}
+
+	/**
+	 * Notify this peer's consumers that the given channel has been closed.
+	 *
+	 * @param channel
+	 * 		The channel that was closed.
+	 */
+	protected void notifyClose(@Nonnull NetChannel<IN, OUT> channel) {
+		reactor.notify(close.getObject(), Event.wrap(channel));
+	}
+
+	/**
+	 * Notify this server's consumers that the server has stopped.
+	 */
+	protected void notifyShutdown() {
+		getReactor().notify(shutdown.getObject(), selfEvent);
+	}
+
+	/**
+	 * Get the {@link Codec} in use.
+	 *
+	 * @return The codec. May be {@literal null}.
+	 */
+	@Nullable
+	protected Codec<Buffer, IN, OUT> getCodec() {
+		return codec;
+	}
+
+	@Nonnull
+	protected Environment getEnvironment() {
+		return env;
+	}
+
+	@Nonnull
+	protected Reactor getReactor() {
+		return reactor;
+	}
+
+	@Nonnull
+	protected Collection<Consumer<NetChannel<IN, OUT>>> getConsumers() {
+		return consumers;
+	}
+
+	@Nonnull
+	protected Registry<NetChannel<IN, OUT>> getChannels() {
+		return netChannels;
+	}
+
+	/**
+	 * Subclasses should implement this method to perform the actual IO channel close.
+	 *
+	 * @param onClose
+	 */
+	protected void doClose(@Nullable Consumer<Boolean> onClose) {
+		getReactor().schedule(onClose, true);
+	}
+
+	/**
+	 * Close the given channel.
+	 *
+	 * @param channel
+	 */
+	protected void doCloseChannel(NetChannel<IN, OUT> channel) {
+		channel.close();
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/NetChannel.java b/reactor-net/src/main/java/reactor/net/NetChannel.java
new file mode 100644
index 0000000..a1777c9
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/NetChannel.java
@@ -0,0 +1,171 @@
+package reactor.net;
+
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.function.batch.BatchConsumer;
+
+import java.net.InetSocketAddress;
+
+/**
+ * {@code NetChannel} implementations handle interacting with the client.
+ *
+ * @author Jon Brisbin
+ */
+public interface NetChannel<IN, OUT> {
+
+	/**
+	 * Get the address of the remote peer.
+	 *
+	 * @return the peer's address
+	 */
+	InetSocketAddress remoteAddress();
+
+	/**
+	 * {@link reactor.core.composable.Stream} of incoming decoded data.
+	 *
+	 * @return input {@link reactor.core.composable.Stream}
+	 */
+	Stream<IN> in();
+
+	/**
+	 * {@link reactor.function.batch.BatchConsumer} for efficiently data to the peer.
+	 *
+	 * @return output {@link reactor.function.batch.BatchConsumer}
+	 */
+	BatchConsumer<OUT> out();
+
+	/**
+	 * When an error of the given type occurs, handle it with the given {@link reactor.function.Consumer}.
+	 *
+	 * @param type
+	 * 		type of error
+	 * @param onError
+	 * 		error handler
+	 * @param <T>
+	 * 		type of the exception
+	 *
+	 * @return {@literal this}
+	 */
+	<T extends Throwable> NetChannel<IN, OUT> when(Class<T> type, Consumer<T> onError);
+
+	/**
+	 * Efficiently consume incoming decoded data.
+	 *
+	 * @param consumer
+	 * 		the incoming data {@link reactor.function.Consumer}
+	 *
+	 * @return {@literal this}
+	 */
+	NetChannel<IN, OUT> consume(Consumer<IN> consumer);
+
+	/**
+	 * Handle incoming data and return the response.
+	 *
+	 * @param fn
+	 * 		request handler
+	 *
+	 * @return {@literal this}
+	 */
+	NetChannel<IN, OUT> receive(Function<IN, OUT> fn);
+
+	/**
+	 * Send data to the peer that passes through the given {@link reactor.core.composable.Stream}.
+	 *
+	 * @param data
+	 * 		the {@link reactor.core.composable.Stream} of data to monitor
+	 *
+	 * @return {@literal this}
+	 */
+	NetChannel<IN, OUT> send(Stream<OUT> data);
+
+	/**
+	 * Send data to the peer.
+	 *
+	 * @param data
+	 * 		the data to send
+	 *
+	 * @return a {@link reactor.core.composable.Promise} indicating when the send operation has completed
+	 */
+	Promise<Void> send(OUT data);
+
+	/**
+	 * Send data to the peer.
+	 *
+	 * @param data
+	 * 		the data to send
+	 *
+	 * @return {@literal this}
+	 */
+	NetChannel<IN, OUT> sendAndForget(OUT data);
+
+	/**
+	 * Send data to the peer and expect a response.
+	 *
+	 * @param data
+	 * 		the data to send
+	 *
+	 * @return a {@link reactor.core.composable.Promise} representing the response from the peer
+	 */
+	Promise<IN> sendAndReceive(OUT data);
+
+	/**
+	 * Close this {@literal NetChannel}.
+	 */
+	Promise<Boolean> close();
+
+	/**
+	 * Close this {@link reactor.net.NetChannel} and invoke the given {@link reactor.function.Consumer} when closed.
+	 *
+	 * @param onClose
+	 */
+	void close(Consumer<Boolean> onClose);
+
+	/**
+	 * Assign event handlers to certain channel lifecycle events.
+	 *
+	 * @return
+	 */
+	ConsumerSpec on();
+
+	/**
+	 * Spec class for assigning multiple event handlers on a channel.
+	 */
+	public static interface ConsumerSpec {
+		/**
+		 * Assign a {@link Runnable} to be invoked when the channel is closed.
+		 *
+		 * @param onClose
+		 * 		the close event handler
+		 *
+		 * @return {@literal this}
+		 */
+		ConsumerSpec close(Runnable onClose);
+
+		/**
+		 * Assign a {@link Runnable} to be invoked when reads have become idle for the given timeout.
+		 *
+		 * @param idleTimeout
+		 * 		the idle timeout
+		 * @param onReadIdle
+		 * 		the idle timeout handler
+		 *
+		 * @return {@literal this}
+		 */
+		ConsumerSpec readIdle(long idleTimeout, Runnable onReadIdle);
+
+		/**
+		 * Assign a {@link Runnable} to be invoked when writes have become idle for the given timeout.
+		 *
+		 * @param idleTimeout
+		 * 		the idle timeout
+		 * @param onWriteIdle
+		 * 		the idle timeout handler
+		 *
+		 * @return {@literal this}
+		 */
+		ConsumerSpec writeIdle(long idleTimeout, Runnable onWriteIdle);
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/NetClient.java b/reactor-net/src/main/java/reactor/net/NetClient.java
new file mode 100644
index 0000000..1bf27e6
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/NetClient.java
@@ -0,0 +1,47 @@
+package reactor.net;
+
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.function.Consumer;
+
+/**
+ * A network-aware client.
+ *
+ * @author Jon Brisbin
+ */
+public interface NetClient<IN, OUT> extends Iterable<NetChannel<IN, OUT>> {
+
+	/**
+	 * Open a channel to the configured address and return a {@link reactor.core.composable.Promise} that will be
+	 * fulfilled with the connected {@link reactor.net.NetChannel}.
+	 *
+	 * @return {@link reactor.core.composable.Promise} that will be completed when connected
+	 */
+	Promise<NetChannel<IN, OUT>> open();
+
+	/**
+	 * Open a channel to the configured address and return a {@link reactor.core.composable.Stream} that will be populated
+	 * by the {@link reactor.net.NetChannel NetChannels} every time a connection or reconnection is made.
+	 *
+	 * @param reconnect
+	 * 		the reconnection strategy to use when disconnects happen
+	 *
+	 * @return
+	 */
+	Stream<NetChannel<IN, OUT>> open(Reconnect reconnect);
+
+	/**
+	 * Close this client and the underlying channel.
+	 */
+	Promise<Boolean> close();
+
+	/**
+	 * Close this client and the underlying channel and invoke the given {@link reactor.function.Consumer} when the
+	 * operation has completed.
+	 *
+	 * @param onClose
+	 * 		consumer to invoke when client is closed
+	 */
+	void close(Consumer<Boolean> onClose);
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/NetServer.java b/reactor-net/src/main/java/reactor/net/NetServer.java
new file mode 100644
index 0000000..04b9b81
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/NetServer.java
@@ -0,0 +1,40 @@
+package reactor.net;
+
+import reactor.core.composable.Promise;
+
+import javax.annotation.Nullable;
+
+/**
+ * A network-aware server.
+ *
+ * @author Jon Brisbin
+ */
+public interface NetServer<IN, OUT> extends Iterable<NetChannel<IN, OUT>> {
+
+	/**
+	 * Start and bind this {@literal NetServer} to the configured listen port.
+	 *
+	 * @return a {@link reactor.core.composable.Promise} that will be complete when the {@link NetServer} is started
+	 */
+	Promise<Boolean> start();
+
+	/**
+	 * Start and bind this {@literal NetServer} to the configured listen port and notify the given {@link
+	 * reactor.function.Consumer} when the bind operation is complete.
+	 *
+	 * @param started
+	 * 		{@link java.lang.Runnable} to invoke when bind operation is complete
+	 *
+	 * @return {@link this}
+	 */
+	NetServer<IN, OUT> start(@Nullable Runnable started);
+
+	/**
+	 * Shutdown this {@literal NetServer} and complete the returned {@link reactor.core.composable.Promise} when shut
+	 * down.
+	 *
+	 * @return a {@link reactor.core.composable.Promise} that will be complete when the {@link NetServer} is shut down
+	 */
+	Promise<Boolean> shutdown();
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/Reconnect.java b/reactor-net/src/main/java/reactor/net/Reconnect.java
new file mode 100644
index 0000000..1fcbdaf
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/Reconnect.java
@@ -0,0 +1,29 @@
+package reactor.net;
+
+import reactor.tuple.Tuple2;
+
+import java.net.InetSocketAddress;
+
+/**
+ * Implementations of this interface will be instantiated by a {@link reactor.function.Supplier} to provide information
+ * to the {@link reactor.net.tcp.TcpClient} whether or not to attempt to reconnect a broken connection.
+ * <p/>
+ * The {@link #reconnect(java.net.InetSocketAddress, int)} method will be invoked, passing the currently-connected
+ * address and the number of times a reconnection has been attempted on this connection. If the client is to reconnect
+ * to a different host, then provide that different address in the return value. If you don't want to try and reconnect
+ * at all, simply return {@code null}.
+ *
+ * @author Jon Brisbin
+ */
+public interface Reconnect {
+
+	/**
+	 * Provide an {@link InetSocketAddress} to which a reconnection attempt should be made.
+	 *
+	 * @param currentAddress the address to which the client is currently connected
+	 * @return a possibly different {@link InetSocketAddress} to which a reconnection attempt should be made and a {@code
+	 *         Long} denoting the time to delay a reconnection attempt
+	 */
+	Tuple2<InetSocketAddress, Long> reconnect(InetSocketAddress currentAddress, int attempt);
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/config/ClientSocketOptions.java b/reactor-net/src/main/java/reactor/net/config/ClientSocketOptions.java
new file mode 100644
index 0000000..1096995
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/config/ClientSocketOptions.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.config;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ClientSocketOptions extends CommonSocketOptions<ClientSocketOptions> {
+}
diff --git a/reactor-net/src/main/java/reactor/net/config/CommonSocketOptions.java b/reactor-net/src/main/java/reactor/net/config/CommonSocketOptions.java
new file mode 100644
index 0000000..9c68c3a
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/config/CommonSocketOptions.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.config;
+
+import reactor.io.Buffer;
+
+/**
+ * Encapsulates common socket options.
+ *
+ * @param <SO> A CommonSocketOptions subclass
+ *
+ * @author Jon Brisbin
+ */
+ at SuppressWarnings("unchecked")
+public abstract class CommonSocketOptions<SO extends CommonSocketOptions<? super SO>> {
+
+	private int     timeout    = 30000;
+	private boolean keepAlive  = true;
+	private int     linger     = 30000;
+	private boolean tcpNoDelay = true;
+	private int     rcvbuf     = Buffer.SMALL_BUFFER_SIZE;
+	private int     sndbuf     = Buffer.SMALL_BUFFER_SIZE;
+
+	/**
+	 * Gets the {@code SO_TIMEOUT} value
+	 *
+	 * @return the timeout value
+	 */
+	public int timeout() {
+		return timeout;
+	}
+
+	/**
+	 * Set the {@code SO_TIMEOUT} value.
+	 *
+	 * @param timeout The {@code SO_TIMEOUT} value.
+	 *
+	 * @return {@code this}
+	 */
+	public SO timeout(int timeout) {
+		this.timeout = timeout;
+		return (SO) this;
+	}
+
+	/**
+	 * Returns a boolean indicating whether or not {@code SO_KEEPALIVE} is enabled
+	 *
+	 * @return {@code true} if keep alive is enabled, {@code false} otherwise
+	 */
+	public boolean keepAlive() {
+		return keepAlive;
+	}
+
+	/**
+	 * Enables or disables {@code SO_KEEPALIVE}.
+	 *
+	 * @param keepAlive {@code true} to enable keepalive, {@code false} to disable keepalive
+	 *
+	 * @return {@code this}
+	 */
+	public SO keepAlive(boolean keepAlive) {
+		this.keepAlive = keepAlive;
+		return (SO) this;
+	}
+
+	/**
+	 * Returns the configuration of {@code SO_LINGER}.
+	 *
+	 * @return the value of {@code SO_LINGER} in seconds
+	 */
+	public int linger() {
+		return linger;
+	}
+
+	/**
+	 * Configures {@code SO_LINGER}
+	 *
+	 * @param linger The linger period in seconds
+	 *
+	 * @return {@code this}
+	 *
+	 */
+	public SO linger(int linger) {
+		this.linger = linger;
+		return (SO) this;
+	}
+
+	/**
+	 * Returns a boolean indicating whether or not {@code TCP_NODELAY} is enabled
+	 *
+	 * @return {@code true} if {@code TCP_NODELAY} is enabled, {@code false} if it is not
+	 */
+	public boolean tcpNoDelay() {
+		return tcpNoDelay;
+	}
+
+	/**
+	 * Enables or disables {@code TCP_NODELAY}
+	 *
+	 * @param tcpNoDelay {@code true} to enable {@code TCP_NODELAY}, {@code false} to disable it
+	 *
+	 * @return {@code this}
+	 */
+	public SO tcpNoDelay(boolean tcpNoDelay) {
+		this.tcpNoDelay = tcpNoDelay;
+		return (SO) this;
+	}
+
+	/**
+	 * Gets the configured {@code SO_RCVBUF} (receive buffer) size
+	 *
+	 * @return The configured receive buffer size
+	 */
+	public int rcvbuf() {
+		return rcvbuf;
+	}
+
+	/**
+	 * Sets the {@code SO_RCVBUF} (receive buffer) size
+	 *
+	 * @param rcvbuf The size of the receive buffer
+	 *
+	 * @return {@code this}
+	 */
+	public SO rcvbuf(int rcvbuf) {
+		this.rcvbuf = rcvbuf;
+		return (SO) this;
+	}
+
+	/**
+	 * Gets the configured {@code SO_SNDBUF} (send buffer) size
+	 *
+	 * @return The configured send buffer size
+	 */
+	public int sndbuf() {
+		return sndbuf;
+	}
+
+	/**
+	 * Sets the {@code SO_SNDBUF} (send buffer) size
+	 *
+	 * @param sndbuf The size of the send buffer
+	 *
+	 * @return {@code this}
+	 */
+	public SO sndbuf(int sndbuf) {
+		this.sndbuf = sndbuf;
+		return (SO) this;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/config/ServerSocketOptions.java b/reactor-net/src/main/java/reactor/net/config/ServerSocketOptions.java
new file mode 100644
index 0000000..5432044
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/config/ServerSocketOptions.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.config;
+
+/**
+ * Encapsulates configuration options for server sockets.
+ *
+ * @author Jon Brisbin
+ */
+public class ServerSocketOptions extends CommonSocketOptions<ServerSocketOptions> {
+
+	private int     backlog   = 1000;
+	private boolean reuseAddr = true;
+
+	/**
+	 * Returns the configured pending connection backlog for the socket.
+	 *
+	 * @return The configured connection backlog size
+	 */
+	public int backlog() {
+		return backlog;
+	}
+
+	/**
+	 * Configures the size of the pending connection backlog for the socket.
+	 *
+	 * @param backlog The size of the backlog
+	 *
+	 * @return {@code this}
+	 */
+	public ServerSocketOptions backlog(int backlog) {
+		this.backlog = backlog;
+		return this;
+	}
+
+	/**
+	 * Returns a boolean indicating whether or not {@code SO_REUSEADDR} is enabled
+	 *
+	 * @return {@code true} if {@code SO_REUSEADDR} is enabled, {@code false} if it is not
+	 */
+	public boolean reuseAddr() {
+		return reuseAddr;
+	}
+
+	/**
+	 * Enables or disables {@code SO_REUSEADDR}.
+	 *
+	 * @param reuseAddr {@code true} to enable {@code SO_REUSEADDR}, {@code false} to disable it
+	 *
+	 * @return {@code this}
+	 */
+	public ServerSocketOptions reuseAddr(boolean reuseAddr) {
+		this.reuseAddr = reuseAddr;
+		return this;
+	}
+}
diff --git a/reactor-net/src/main/java/reactor/net/config/SslOptions.java b/reactor-net/src/main/java/reactor/net/config/SslOptions.java
new file mode 100644
index 0000000..d4f8338
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/config/SslOptions.java
@@ -0,0 +1,101 @@
+package reactor.net.config;
+
+import reactor.function.Supplier;
+import reactor.util.Assert;
+
+import javax.net.ssl.TrustManager;
+import java.io.File;
+
+/**
+ * Helper class encapsulating common SSL configuration options.
+ *
+ * @author Jon Brisbin
+ */
+public class SslOptions {
+
+	private File   keystoreFile;
+	private String keystorePasswd;
+	private String keyManagerPasswd;
+	private String keyManagerFactoryAlgorithm = "SunX509";
+	private Supplier<TrustManager[]> trustManagers;
+	private String                   trustManagerPasswd;
+	private String trustManagerFactoryAlgorithm = "SunX509";
+	private String sslProtocol                  = "TLS";
+
+	public String keystoreFile() {
+		return (null != keystoreFile ? keystoreFile.getPath() : null);
+	}
+
+	public SslOptions keystoreFile(String keystoreFile) {
+		this.keystoreFile = new File(keystoreFile);
+		Assert.isTrue(this.keystoreFile.exists(), "No keystore file found at path " + this.keystoreFile.getAbsolutePath());
+		return this;
+	}
+
+	public String keystorePasswd() {
+		return keystorePasswd;
+	}
+
+	public SslOptions keystorePasswd(String keystorePasswd) {
+		this.keystorePasswd = keystorePasswd;
+		return this;
+	}
+
+	public String keyManagerPasswd() {
+		return keyManagerPasswd;
+	}
+
+	public SslOptions keyManagerPasswd(String keyManagerPasswd) {
+		this.keyManagerPasswd = keyManagerPasswd;
+		return this;
+	}
+
+	public String keyManagerFactoryAlgorithm() {
+		return keyManagerFactoryAlgorithm;
+	}
+
+	public SslOptions keyManagerFactoryAlgorithm(String keyManagerFactoryAlgorithm) {
+		Assert.notNull(keyManagerFactoryAlgorithm, "KeyManagerFactory algorithm cannot be null");
+		this.keyManagerFactoryAlgorithm = keyManagerFactoryAlgorithm;
+		return this;
+	}
+
+	public String trustManagerPasswd() {
+		return trustManagerPasswd;
+	}
+
+	public SslOptions trustManagerPasswd(String trustManagerPasswd) {
+		this.trustManagerPasswd = trustManagerPasswd;
+		return this;
+	}
+
+	public Supplier<TrustManager[]> trustManagers() {
+		return trustManagers;
+	}
+
+	public SslOptions trustManagers(Supplier<TrustManager[]> trustManagers) {
+		this.trustManagers = trustManagers;
+		return this;
+	}
+
+	public String trustManagerFactoryAlgorithm() {
+		return trustManagerFactoryAlgorithm;
+	}
+
+	public SslOptions trustManagerFactoryAlgorithm(String trustManagerFactoryAlgorithm) {
+		Assert.notNull(trustManagerFactoryAlgorithm, "TrustManagerFactory algorithm cannot be null");
+		this.trustManagerFactoryAlgorithm = trustManagerFactoryAlgorithm;
+		return this;
+	}
+
+	public String sslProtocol() {
+		return sslProtocol;
+	}
+
+	public SslOptions sslProtocol(String sslProtocol) {
+		Assert.notNull(sslProtocol, "SSL protocol cannot be null");
+		this.sslProtocol = sslProtocol;
+		return this;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/config/package-info.java b/reactor-net/src/main/java/reactor/net/config/package-info.java
new file mode 100644
index 0000000..2826180
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/config/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Configuration of the various TCP parameters for clients and servers.
+ */
+package reactor.net.config;
\ No newline at end of file
diff --git a/reactor-net/src/main/java/reactor/net/encoding/syslog/SyslogCodec.java b/reactor-net/src/main/java/reactor/net/encoding/syslog/SyslogCodec.java
new file mode 100644
index 0000000..050018d
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/encoding/syslog/SyslogCodec.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.encoding.syslog;
+
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * A coded for consuming syslog messages. This codec produces no output, i.e.  its encoding
+ * function returns {@code null}.
+ *
+ * @author Jon Brisbin
+ */
+public class SyslogCodec implements Codec<Buffer, SyslogMessage, Void> {
+
+	private static final int MAXIMUM_SEVERITY = 7;
+	private static final int MAXIMUM_FACILITY = 23;
+	private static final int MINIMUM_PRI      = 0;
+	private static final int MAXIMUM_PRI      = (MAXIMUM_FACILITY * 8) + MAXIMUM_SEVERITY;
+	private static final int DEFAULT_PRI      = 13;
+
+	private static final Function<Void, Buffer> ENDCODER = new Function<Void, Buffer>() {
+		@Override
+		public Buffer apply(Void v) {
+			return null;
+		}
+	};
+
+	@Override
+	public Function<Buffer, SyslogMessage> decoder(Consumer<SyslogMessage> next) {
+		return new SyslogMessageDecoder(next);
+	}
+
+	@Override
+	public Function<Void, Buffer> encoder() {
+		return ENDCODER;
+	}
+
+	private class SyslogMessageDecoder implements Function<Buffer, SyslogMessage> {
+		private final Calendar cal  = Calendar.getInstance();
+		private final int      year = cal.get(Calendar.YEAR);
+		private final Consumer<SyslogMessage> next;
+		private       Buffer.View             remainder;
+
+		private SyslogMessageDecoder(Consumer<SyslogMessage> next) {
+			this.next = next;
+		}
+
+		@Override
+		public SyslogMessage apply(Buffer buffer) {
+			return parse(buffer);
+		}
+
+		private SyslogMessage parse(Buffer buffer) {
+			String line = null;
+			if (null != remainder) {
+				line = remainder.get().asString();
+			}
+
+			int start = 0;
+			for (Buffer.View view : buffer.split('\n', false)) {
+				Buffer b = view.get();
+				if (b.last() != '\n') {
+					remainder = view;
+					return null;
+				}
+				String s = b.asString();
+				if (null != line) {
+					line += s;
+				} else {
+					line = s;
+				}
+				if (line.isEmpty()) {
+					continue;
+				}
+
+				int priority = DEFAULT_PRI;
+				int facility = priority / 8;
+				int severity = priority % 8;
+
+				int priStart = line.indexOf('<', start);
+				int priEnd = line.indexOf('>', start + 1);
+				if (priStart == 0) {
+					int pri = Buffer.parseInt(b, 1, priEnd);
+					if (pri >= MINIMUM_PRI && pri <= MAXIMUM_PRI) {
+						priority = pri;
+						facility = priority / 8;
+						severity = priority % 8;
+					}
+					start = 4;
+				}
+
+				Date tstamp = parseRfc3414Date(b, start, start + 15);
+				String host = null;
+				if (null != tstamp) {
+					start += 16;
+					int end = line.indexOf(' ', start);
+					host = line.substring(start, end);
+					if (null != host) {
+						start += host.length() + 1;
+					}
+				}
+
+				String msg = line.substring(start);
+
+				SyslogMessage syslogMsg = new SyslogMessage(line,
+																										priority,
+																										facility,
+																										severity,
+																										tstamp,
+																										host,
+																										msg);
+				if (null != next) {
+					next.accept(syslogMsg);
+				} else {
+					return syslogMsg;
+				}
+
+				line = null;
+				start = 0;
+			}
+
+			return null;
+		}
+
+		private Date parseRfc3414Date(Buffer b, int start, int end) {
+			b.snapshot();
+
+			b.byteBuffer().limit(end);
+			b.byteBuffer().position(start);
+
+			int month = -1;
+			int day = -1;
+			int hr = -1;
+			int min = -1;
+			int sec = -1;
+
+			switch (b.read()) {
+				case 'A': // Apr, Aug
+					switch (b.read()) {
+						case 'p':
+							month = Calendar.APRIL;
+							b.read();
+							break;
+						default:
+							month = Calendar.AUGUST;
+							b.read();
+					}
+					break;
+				case 'D': // Dec
+					month = Calendar.DECEMBER;
+					b.read();
+					b.read();
+					break;
+				case 'F': // Feb
+					month = Calendar.FEBRUARY;
+					b.read();
+					b.read();
+					break;
+				case 'J': // Jan, Jun, Jul
+					switch (b.read()) {
+						case 'a':
+							month = Calendar.JANUARY;
+							b.read();
+							break;
+						default:
+							switch (b.read()) {
+								case 'n':
+									month = Calendar.JUNE;
+									break;
+								default:
+									month = Calendar.JULY;
+							}
+					}
+					break;
+				case 'M': // Mar, May
+					b.read();
+					switch (b.read()) {
+						case 'r':
+							month = Calendar.MARCH;
+							break;
+						default:
+							month = Calendar.MAY;
+					}
+					break;
+				case 'N': // Nov
+					month = Calendar.NOVEMBER;
+					b.read();
+					b.read();
+					break;
+				case 'O': // Oct
+					month = Calendar.OCTOBER;
+					b.read();
+					b.read();
+					break;
+				case 'S': // Sep
+					month = Calendar.SEPTEMBER;
+					b.read();
+					b.read();
+					break;
+				default:
+					return null;
+			}
+
+			while (b.read() == ' ') {
+			}
+
+			int dayStart = b.position() - 1;
+			while (b.read() != ' ') {
+			}
+			int dayEnd = b.position() - 1;
+			day = Buffer.parseInt(b, dayStart, dayEnd);
+
+			while (b.read() == ' ') {
+			}
+
+			int timeStart = b.position() - 1;
+			hr = Buffer.parseInt(b, timeStart, timeStart + 2);
+			min = Buffer.parseInt(b, timeStart + 3, timeStart + 5);
+			sec = Buffer.parseInt(b, timeStart + 6, timeStart + 8);
+
+			try {
+				if (month < 0 || day < 0 || hr < 0 || min < 0 || sec < 0) {
+					return null;
+				} else {
+					cal.set(year, month, day, hr, min, sec);
+					return cal.getTime();
+				}
+			} finally {
+				b.reset();
+			}
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/encoding/syslog/SyslogMessage.java b/reactor-net/src/main/java/reactor/net/encoding/syslog/SyslogMessage.java
new file mode 100644
index 0000000..7e813de
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/encoding/syslog/SyslogMessage.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.encoding.syslog;
+
+import java.util.Date;
+
+/**
+ * An object representation of a syslog message
+ *
+ * @author Jon Brisbin
+ */
+public class SyslogMessage {
+
+	private final String raw;
+	private final int    priority;
+	private final int    facility;
+	private final int    severity;
+	private final Date   timestamp;
+	private final String host;
+	private final String message;
+
+	/**
+	 * Creates a new syslog message.
+	 *
+	 * @param raw The raw, unparsed message
+	 * @param priority The message's priority
+	 * @param facility The message's facility
+	 * @param severity The message's severity
+	 * @param timestamp The message's timestamp
+	 * @param host The host from which the message originated
+	 * @param message The actual message
+	 */
+	public SyslogMessage(String raw,
+											 int priority,
+											 int facility,
+											 int severity,
+											 Date timestamp,
+											 String host,
+											 String message) {
+		this.raw = raw;
+		this.priority = priority;
+		this.facility = facility;
+		this.severity = severity;
+		this.timestamp = timestamp;
+		this.host = host;
+		this.message = message;
+	}
+
+	/**
+	 * Returns the priority assigned to the message
+	 *
+	 * @return The message's priority
+	 */
+	public int getPriority() {
+		return priority;
+	}
+
+	/**
+	 * Returns the facility that sent the message
+	 *
+	 * @return The message's facility
+	 */
+	public int getFacility() {
+		return facility;
+	}
+
+	/**
+	 * Returns the severity assigned to the message
+	 *
+	 * @return The message's severity
+	 */
+	public int getSeverity() {
+		return severity;
+	}
+
+	/**
+	 * Returns the timestamp for the message
+	 *
+	 * @return The message's timestamp
+	 */
+	public Date getTimestamp() {
+		return timestamp;
+	}
+
+	/**
+	 * Returns the host from which the message originated
+	 *
+	 * @return The message's host
+	 */
+	public String getHost() {
+		return host;
+	}
+
+	/**
+	 * Returns the actual message
+	 *
+	 * @return The text-based message
+	 */
+	public String getMessage() {
+		return message;
+	}
+
+	@Override
+	public String toString() {
+		return raw;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/encoding/syslog/package-info.java b/reactor-net/src/main/java/reactor/net/encoding/syslog/package-info.java
new file mode 100644
index 0000000..5b66f24
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/encoding/syslog/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Decoding using the syslog format.
+ */
+package reactor.net.encoding.syslog;
\ No newline at end of file
diff --git a/reactor-net/src/main/java/reactor/net/netty/NettyClientSocketOptions.java b/reactor-net/src/main/java/reactor/net/netty/NettyClientSocketOptions.java
new file mode 100644
index 0000000..9630423
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/NettyClientSocketOptions.java
@@ -0,0 +1,34 @@
+package reactor.net.netty;
+
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.nio.NioEventLoopGroup;
+import reactor.function.Consumer;
+import reactor.net.config.ClientSocketOptions;
+
+/**
+ * @author Jon Brisbin
+ */
+public class NettyClientSocketOptions extends ClientSocketOptions {
+
+	private Consumer<ChannelPipeline> pipelineConfigurer;
+	private NioEventLoopGroup         eventLoopGroup;
+
+	public Consumer<ChannelPipeline> pipelineConfigurer() {
+		return pipelineConfigurer;
+	}
+
+	public NettyClientSocketOptions pipelineConfigurer(Consumer<ChannelPipeline> pipelineConfigurer) {
+		this.pipelineConfigurer = pipelineConfigurer;
+		return this;
+	}
+
+	public NioEventLoopGroup eventLoopGroup() {
+		return eventLoopGroup;
+	}
+
+	public NettyClientSocketOptions eventLoopGroup(NioEventLoopGroup eventLoopGroup) {
+		this.eventLoopGroup = eventLoopGroup;
+		return this;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/netty/NettyEventLoopDispatcher.java b/reactor-net/src/main/java/reactor/net/netty/NettyEventLoopDispatcher.java
new file mode 100644
index 0000000..6af3c8c
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/NettyEventLoopDispatcher.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.netty;
+
+import io.netty.channel.EventLoop;
+import reactor.event.dispatch.AbstractMultiThreadDispatcher;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@code Dispatcher} that runs tasks on a Netty {@link EventLoop}.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+ at SuppressWarnings({"rawtypes"})
+public class NettyEventLoopDispatcher extends AbstractMultiThreadDispatcher {
+
+	private final EventLoop eventLoop;
+
+	/**
+	 * Creates a new Netty event loop-based dispatcher that will run tasks on the given {@code eventLoop} with the given
+	 * {@code backlog} size.
+	 *
+	 * @param eventLoop
+	 * 		The event loop to run tasks on
+	 * @param backlog
+	 * 		The size of the backlog of unexecuted tasks
+	 */
+	public NettyEventLoopDispatcher(EventLoop eventLoop, int backlog) {
+		super(1, backlog);
+		this.eventLoop = eventLoop;
+	}
+
+	@Override
+	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
+		shutdown();
+		try {
+			return eventLoop.awaitTermination(timeout, timeUnit);
+		} catch(InterruptedException e) {
+			Thread.currentThread().interrupt();
+		}
+		return false;
+	}
+
+	@Override
+	public void shutdown() {
+		eventLoop.shutdownGracefully();
+		super.shutdown();
+	}
+
+	@Override
+	public void halt() {
+		eventLoop.shutdownGracefully();
+		super.halt();
+	}
+
+	@Override
+	protected void execute(Task task) {
+		eventLoop.execute(task);
+	}
+
+	@Override
+	public void execute(Runnable command) {
+		eventLoop.execute(command);
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/netty/NettyNetChannel.java b/reactor-net/src/main/java/reactor/net/netty/NettyNetChannel.java
new file mode 100644
index 0000000..72c7b3c
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/NettyNetChannel.java
@@ -0,0 +1,161 @@
+package reactor.net.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.*;
+import io.netty.handler.timeout.IdleState;
+import io.netty.handler.timeout.IdleStateEvent;
+import io.netty.handler.timeout.IdleStateHandler;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.event.Event;
+import reactor.event.dispatch.Dispatcher;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.AbstractNetChannel;
+import reactor.tuple.Tuple;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * {@link reactor.net.NetChannel} implementation that delegates to Netty.
+ *
+ * @author Jon Brisbin
+ */
+public class NettyNetChannel<IN, OUT> extends AbstractNetChannel<IN, OUT> {
+
+	private final Channel ioChannel;
+
+	private volatile boolean closing = false;
+
+	public NettyNetChannel(@Nonnull Environment env,
+	                       @Nullable Codec<Buffer, IN, OUT> codec,
+	                       @Nonnull Dispatcher ioDispatcher,
+	                       @Nonnull Reactor eventsReactor,
+	                       @Nonnull Channel ioChannel) {
+		super(env, codec, ioDispatcher, eventsReactor);
+		this.ioChannel = ioChannel;
+	}
+
+	public boolean isClosing() {
+		return closing;
+	}
+
+	@Override
+	public InetSocketAddress remoteAddress() {
+		return (InetSocketAddress) ioChannel.remoteAddress();
+	}
+
+	@Override
+	public void close(@Nullable final Consumer<Boolean> onClose) {
+		if (closing) {
+			return;
+		}
+		closing = true;
+		ioChannel.close().addListener(new ChannelFutureListener() {
+			@Override
+			public void operationComplete(ChannelFuture future) throws Exception {
+				if (null != onClose) {
+					getEventsReactor().schedule(onClose, future.isSuccess());
+				} else if (!future.isSuccess()) {
+					log.error(future.cause().getMessage(), future.cause());
+				}
+				closing = false;
+			}
+		});
+	}
+
+	@Override
+	public ConsumerSpec on() {
+		return new NettyConsumerSpec();
+	}
+
+	@Override
+	protected void write(ByteBuffer data, Deferred<Void, Promise<Void>> onComplete, boolean flush) {
+		ByteBuf buf = ioChannel.alloc().buffer(data.remaining());
+		buf.writeBytes(data);
+		write(buf, onComplete, flush);
+	}
+
+	@Override
+	protected void write(Object data, final Deferred<Void, Promise<Void>> onComplete, boolean flush) {
+		ChannelFuture writeFuture = ioChannel.write(Tuple.of(data, flush));
+		writeFuture.addListener(new ChannelFutureListener() {
+			@Override
+			public void operationComplete(ChannelFuture future) throws Exception {
+				boolean success = future.isSuccess();
+
+				if (!success) {
+					Throwable t = future.cause();
+					getEventsReactor().notify(t.getClass(), Event.wrap(t));
+					if (null != onComplete) {
+						onComplete.accept(t);
+					}
+				} else if (null != onComplete) {
+					onComplete.accept((Void) null);
+				}
+			}
+		});
+	}
+
+	@Override
+	protected void flush() {
+		ioChannel.write(Tuple.of(null, true));
+	}
+
+	@Override
+	public String toString() {
+		return "NettyNetChannel{" +
+				"channel=" + ioChannel +
+				'}';
+	}
+
+	private class NettyConsumerSpec implements ConsumerSpec {
+		@Override
+		public ConsumerSpec close(final Runnable onClose) {
+			ioChannel.pipeline().addLast(new ChannelDuplexHandler() {
+				@Override
+				public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+					onClose.run();
+					super.channelInactive(ctx);
+				}
+			});
+			return this;
+		}
+
+		@Override
+		public ConsumerSpec readIdle(long idleTimeout, final Runnable onReadIdle) {
+			ioChannel.pipeline().addFirst(new IdleStateHandler(idleTimeout, 0, 0, TimeUnit.MILLISECONDS) {
+				@Override
+				protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
+					if (evt.state() == IdleState.READER_IDLE) {
+						onReadIdle.run();
+					}
+					super.channelIdle(ctx, evt);
+				}
+			});
+			return this;
+		}
+
+		@Override
+		public ConsumerSpec writeIdle(long idleTimeout, final Runnable onWriteIdle) {
+			ioChannel.pipeline().addLast(new IdleStateHandler(0, idleTimeout, 0, TimeUnit.MILLISECONDS) {
+				@Override
+				protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
+					if (evt.state() == IdleState.WRITER_IDLE) {
+						onWriteIdle.run();
+					}
+					super.channelIdle(ctx, evt);
+				}
+			});
+			return this;
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/netty/NettyNetChannelInboundHandler.java b/reactor-net/src/main/java/reactor/net/netty/NettyNetChannelInboundHandler.java
new file mode 100644
index 0000000..f934577
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/NettyNetChannelInboundHandler.java
@@ -0,0 +1,109 @@
+package reactor.net.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.io.Buffer;
+import reactor.net.AbstractNetChannel;
+
+/**
+ * Netty {@link io.netty.channel.ChannelInboundHandler} implementation that passes data to a Reactor {@link
+ * reactor.net.AbstractNetChannel}.
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ * @author Andy Wilkinson
+ */
+public class NettyNetChannelInboundHandler extends ChannelInboundHandlerAdapter {
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+
+	private volatile AbstractNetChannel netChannel;
+	private volatile ByteBuf            remainder;
+
+	public NettyNetChannelInboundHandler() {
+	}
+
+	public AbstractNetChannel getNetChannel() {
+		return netChannel;
+	}
+
+	public NettyNetChannelInboundHandler setNetChannel(AbstractNetChannel netChannel) {
+		this.netChannel = netChannel;
+		return this;
+	}
+
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+		if (!ByteBuf.class.isInstance(msg) || null == netChannel.getDecoder()) {
+			netChannel.notifyRead(msg);
+			return;
+		}
+
+		ByteBuf data = (ByteBuf) msg;
+		if (remainder == null) {
+			try {
+				passToConnection(data);
+			} finally {
+				if (data.isReadable()) {
+					remainder = data;
+				} else {
+					data.release();
+				}
+			}
+			return;
+		}
+
+		if (!bufferHasSufficientCapacity(remainder, data)) {
+			ByteBuf combined = createCombinedBuffer(remainder, data, ctx);
+			remainder.release();
+			remainder = combined;
+		} else {
+			remainder.writeBytes(data);
+		}
+		data.release();
+
+		try {
+			passToConnection(remainder);
+		} finally {
+			if (remainder.isReadable()) {
+				remainder.discardSomeReadBytes();
+			} else {
+				remainder.release();
+				remainder = null;
+			}
+		}
+	}
+
+	@Override
+	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+		if ("Broken pipe".equals(cause.getMessage()) || "Connection reset by peer".equals(cause.getMessage())) {
+			if (log.isDebugEnabled()) {
+				log.debug(ctx.channel().toString() + " " + cause.getMessage());
+			}
+		}
+		netChannel.notifyError(cause);
+		ctx.close();
+	}
+
+	private boolean bufferHasSufficientCapacity(ByteBuf receiver, ByteBuf provider) {
+		return receiver.writerIndex() <= receiver.maxCapacity() - provider.readableBytes();
+	}
+
+	private ByteBuf createCombinedBuffer(ByteBuf partOne, ByteBuf partTwo, ChannelHandlerContext ctx) {
+		ByteBuf combined = ctx.alloc().buffer(partOne.readableBytes() + partTwo.readableBytes());
+		combined.writeBytes(partOne);
+		combined.writeBytes(partTwo);
+		return combined;
+	}
+
+	private void passToConnection(ByteBuf data) {
+		Buffer b = new Buffer(data.nioBuffer());
+		int start = b.position();
+		netChannel.read(b);
+		data.skipBytes(b.position() - start);
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/netty/NettyNetChannelOutboundHandler.java b/reactor-net/src/main/java/reactor/net/netty/NettyNetChannelOutboundHandler.java
new file mode 100644
index 0000000..d97459b
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/NettyNetChannelOutboundHandler.java
@@ -0,0 +1,28 @@
+package reactor.net.netty;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelOutboundHandlerAdapter;
+import io.netty.channel.ChannelPromise;
+import reactor.tuple.Tuple2;
+
+/**
+ * @author Jon Brisbin
+ */
+public class NettyNetChannelOutboundHandler extends ChannelOutboundHandlerAdapter {
+	@SuppressWarnings("unchecked")
+	@Override
+	public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+		if (Tuple2.class.isInstance(msg)) {
+			Tuple2<Object, Boolean> tup = (Tuple2<Object, Boolean>) msg;
+			if (null != tup.getT1()) {
+				super.write(ctx, tup.getT1(), promise);
+			}
+			if (tup.getT2()) {
+				ctx.flush();
+			}
+		} else {
+			super.write(ctx, msg, promise);
+			ctx.flush();
+		}
+	}
+}
diff --git a/reactor-net/src/main/java/reactor/net/netty/NettyServerSocketOptions.java b/reactor-net/src/main/java/reactor/net/netty/NettyServerSocketOptions.java
new file mode 100644
index 0000000..07a4fa9
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/NettyServerSocketOptions.java
@@ -0,0 +1,36 @@
+package reactor.net.netty;
+
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.nio.NioEventLoopGroup;
+import reactor.function.Consumer;
+import reactor.net.config.ServerSocketOptions;
+
+/**
+ * Extends standard {@link ServerSocketOptions} with Netty-specific options.
+ *
+ * @author Jon Brisbin
+ */
+public class NettyServerSocketOptions extends ServerSocketOptions {
+
+	private Consumer<ChannelPipeline> pipelineConfigurer;
+	private NioEventLoopGroup         eventLoopGroup;
+
+	public Consumer<ChannelPipeline> pipelineConfigurer() {
+		return pipelineConfigurer;
+	}
+
+	public NettyServerSocketOptions pipelineConfigurer(Consumer<ChannelPipeline> pipelineConfigurer) {
+		this.pipelineConfigurer = pipelineConfigurer;
+		return this;
+	}
+
+	public NioEventLoopGroup eventLoopGroup() {
+		return eventLoopGroup;
+	}
+
+	public NettyServerSocketOptions eventLoopGroup(NioEventLoopGroup eventLoopGroup) {
+		this.eventLoopGroup = eventLoopGroup;
+		return this;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/netty/package-info.java b/reactor-net/src/main/java/reactor/net/netty/package-info.java
new file mode 100644
index 0000000..34768d9
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Implementations of the various TCP abstractions based on Netty.
+ *
+ * @see <a href="https://github.com/netty/netty">https://github.com/netty/netty</a>
+ */
+package reactor.net.netty;
\ No newline at end of file
diff --git a/reactor-net/src/main/java/reactor/net/netty/tcp/NettyTcpClient.java b/reactor-net/src/main/java/reactor/net/netty/tcp/NettyTcpClient.java
new file mode 100644
index 0000000..abc1a99
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/tcp/NettyTcpClient.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.netty.tcp;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.FutureListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.core.composable.spec.Promises;
+import reactor.core.composable.spec.Streams;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.NetChannel;
+import reactor.net.Reconnect;
+import reactor.net.config.ClientSocketOptions;
+import reactor.net.config.SslOptions;
+import reactor.net.netty.*;
+import reactor.net.tcp.TcpClient;
+import reactor.net.tcp.ssl.SSLEngineSupplier;
+import reactor.support.NamedDaemonThreadFactory;
+import reactor.tuple.Tuple2;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLEngine;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A Netty-based {@code TcpClient}.
+ *
+ * @param <IN>
+ * 		The type that will be received by this client
+ * @param <OUT>
+ * 		The type that will be sent by this client
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class NettyTcpClient<IN, OUT> extends TcpClient<IN, OUT> {
+
+	private final Logger log = LoggerFactory.getLogger(NettyTcpClient.class);
+
+	private final NettyClientSocketOptions nettyOptions;
+	private final Bootstrap                bootstrap;
+	private final EventLoopGroup           ioGroup;
+	private final Supplier<ChannelFuture>  connectionSupplier;
+
+	private volatile InetSocketAddress connectAddress;
+	private volatile boolean           closing;
+
+	/**
+	 * Creates a new NettyTcpClient that will use the given {@code env} for configuration and the given {@code reactor} to
+	 * send events. The number of IO threads used by the client is configured by the environment's {@code
+	 * reactor.tcp.ioThreadCount} property. In its absence the number of IO threads will be equal to the {@link
+	 * Environment#PROCESSORS number of available processors}. </p> The client will connect to the given {@code
+	 * connectAddress}, configuring its socket using the given {@code opts}. The given {@code codec} will be used for
+	 * encoding and decoding of data.
+	 *
+	 * @param env
+	 * 		The configuration environment
+	 * @param reactor
+	 * 		The reactor used to send events
+	 * @param connectAddress
+	 * 		The address the client will connect to
+	 * @param options
+	 * 		The configuration options for the client's socket
+	 * @param sslOptions
+	 * 		The SSL configuration options for the client's socket
+	 * @param codec
+	 * 		The codec used to encode and decode data
+	 * @param consumers
+	 * 		The consumers that will interact with the connection
+	 */
+	public NettyTcpClient(@Nonnull Environment env,
+	                      @Nonnull Reactor reactor,
+	                      @Nonnull InetSocketAddress connectAddress,
+	                      @Nonnull final ClientSocketOptions options,
+	                      @Nullable final SslOptions sslOptions,
+	                      @Nullable Codec<Buffer, IN, OUT> codec,
+	                      @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		super(env, reactor, connectAddress, options, sslOptions, codec, consumers);
+		this.connectAddress = connectAddress;
+
+		if (options instanceof NettyClientSocketOptions) {
+			this.nettyOptions = (NettyClientSocketOptions) options;
+		} else {
+			this.nettyOptions = null;
+
+		}
+		if (null != nettyOptions && null != nettyOptions.eventLoopGroup()) {
+			this.ioGroup = nettyOptions.eventLoopGroup();
+		} else {
+			int ioThreadCount = env.getProperty("reactor.tcp.ioThreadCount", Integer.class, Environment.PROCESSORS);
+			this.ioGroup = new NioEventLoopGroup(ioThreadCount, new NamedDaemonThreadFactory("reactor-tcp-io"));
+		}
+
+		this.bootstrap = new Bootstrap()
+				.group(ioGroup)
+				.channel(NioSocketChannel.class)
+				.option(ChannelOption.SO_RCVBUF, options.rcvbuf())
+				.option(ChannelOption.SO_SNDBUF, options.sndbuf())
+				.option(ChannelOption.SO_KEEPALIVE, options.keepAlive())
+				.option(ChannelOption.SO_LINGER, options.linger())
+				.option(ChannelOption.TCP_NODELAY, options.tcpNoDelay())
+				.remoteAddress(this.connectAddress)
+				.handler(new ChannelInitializer<SocketChannel>() {
+					@Override
+					public void initChannel(final SocketChannel ch) throws Exception {
+						ch.config().setConnectTimeoutMillis(options.timeout());
+
+						if (null != sslOptions) {
+							SSLEngine ssl = new SSLEngineSupplier(sslOptions, true).get();
+							if (log.isDebugEnabled()) {
+								log.debug("SSL enabled using keystore {}",
+								          (null != sslOptions.keystoreFile() ? sslOptions.keystoreFile() : "<DEFAULT>"));
+							}
+							ch.pipeline().addLast(new SslHandler(ssl));
+						}
+						if (null != nettyOptions && null != nettyOptions.pipelineConfigurer()) {
+							nettyOptions.pipelineConfigurer().accept(ch.pipeline());
+						}
+						ch.pipeline().addLast(createChannelHandlers(ch));
+					}
+				});
+
+		this.connectionSupplier = new Supplier<ChannelFuture>() {
+			@Override
+			public ChannelFuture get() {
+				if (!closing) {
+					return bootstrap.connect(getConnectAddress());
+				} else {
+					return null;
+				}
+			}
+		};
+	}
+
+	@Override
+	public Promise<NetChannel<IN, OUT>> open() {
+		final Deferred<NetChannel<IN, OUT>, Promise<NetChannel<IN, OUT>>> connection
+				= Promises.defer(getEnvironment(), getReactor().getDispatcher());
+
+		openChannel(new ConnectingChannelListener(connection));
+
+		return connection.compose();
+	}
+
+	@Override
+	public Stream<NetChannel<IN, OUT>> open(final Reconnect reconnect) {
+		final Deferred<NetChannel<IN, OUT>, Stream<NetChannel<IN, OUT>>> connections
+				= Streams.defer(getEnvironment(), getReactor().getDispatcher());
+
+		openChannel(new ReconnectingChannelListener(connectAddress, reconnect, connections));
+
+		return connections.compose();
+	}
+
+	@Override
+	public void close(@Nullable final Consumer<Boolean> onClose) {
+		if (null != nettyOptions && null != nettyOptions.eventLoopGroup()) {
+			ioGroup.submit(new Runnable() {
+				@Override
+				public void run() {
+					if (null != onClose) {
+						onClose.accept(true);
+					}
+				}
+			});
+		} else {
+			ioGroup.shutdownGracefully().addListener(new FutureListener<Object>() {
+				@Override
+				public void operationComplete(Future<Object> future) throws Exception {
+					if (null != onClose) {
+						onClose.accept(future.isDone() && future.isSuccess());
+					}
+				}
+			});
+		}
+	}
+
+	@Override
+	protected <C> NetChannel<IN, OUT> createChannel(C ioChannel) {
+		SocketChannel ch = (SocketChannel) ioChannel;
+		int backlog = getEnvironment().getProperty("reactor.tcp.connectionReactorBacklog", Integer.class, 128);
+
+		return new NettyNetChannel<IN, OUT>(
+				getEnvironment(),
+				getCodec(),
+				new NettyEventLoopDispatcher(ch.eventLoop(), backlog),
+				getReactor(),
+				ch
+		);
+	}
+
+	protected ChannelHandler[] createChannelHandlers(SocketChannel ioChannel) {
+		NettyNetChannel<IN, OUT> conn = (NettyNetChannel<IN, OUT>) createChannel(ioChannel);
+		NettyNetChannelInboundHandler readHandler = new NettyNetChannelInboundHandler()
+				.setNetChannel(conn);
+		NettyNetChannelOutboundHandler writeHandler = new NettyNetChannelOutboundHandler();
+
+		return new ChannelHandler[]{readHandler, writeHandler};
+	}
+
+	private void openChannel(ChannelFutureListener listener) {
+		ChannelFuture channel = connectionSupplier.get();
+		if (null != channel && null != listener) {
+			channel.addListener(listener);
+		}
+	}
+
+	private class ConnectingChannelListener implements ChannelFutureListener {
+		private final Deferred<NetChannel<IN, OUT>, Promise<NetChannel<IN, OUT>>> connection;
+
+		private ConnectingChannelListener(Deferred<NetChannel<IN, OUT>, Promise<NetChannel<IN, OUT>>> connection) {
+			this.connection = connection;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public void operationComplete(ChannelFuture future) throws Exception {
+			if (!future.isSuccess()) {
+				if (log.isErrorEnabled()) {
+					log.error(future.cause().getMessage(), future.cause());
+				}
+				connection.accept(future.cause());
+				return;
+			}
+
+			if (log.isInfoEnabled()) {
+				log.info("CONNECTED: " + future.channel());
+			}
+
+			NettyNetChannelInboundHandler inboundHandler = future.channel()
+			                                                     .pipeline()
+			                                                     .get(NettyNetChannelInboundHandler.class);
+			final NetChannel<IN, OUT> ch = inboundHandler.getNetChannel();
+
+			future.channel().closeFuture().addListener(new ChannelFutureListener() {
+				@Override
+				public void operationComplete(ChannelFuture future) throws Exception {
+					if (log.isInfoEnabled()) {
+						log.info("CLOSED: " + future.channel());
+					}
+					notifyClose(ch);
+				}
+			});
+
+			future.channel().eventLoop().submit(new Runnable() {
+				@Override
+				public void run() {
+					connection.accept(ch);
+				}
+			});
+		}
+	}
+
+	private class ReconnectingChannelListener implements ChannelFutureListener {
+
+		private final AtomicInteger attempts = new AtomicInteger(0);
+
+		private final Reconnect                                                  reconnect;
+		private final Deferred<NetChannel<IN, OUT>, Stream<NetChannel<IN, OUT>>> connections;
+
+		private volatile InetSocketAddress connectAddress;
+
+		private ReconnectingChannelListener(InetSocketAddress connectAddress,
+		                                    Reconnect reconnect,
+		                                    Deferred<NetChannel<IN, OUT>, Stream<NetChannel<IN, OUT>>> connections) {
+			this.connectAddress = connectAddress;
+			this.reconnect = reconnect;
+			this.connections = connections;
+		}
+
+		@SuppressWarnings("unchecked")
+		@Override
+		public void operationComplete(final ChannelFuture future) throws Exception {
+			if (!future.isSuccess()) {
+				int attempt = attempts.incrementAndGet();
+				Tuple2<InetSocketAddress, Long> tup = reconnect.reconnect(connectAddress, attempt);
+				if (null == tup) {
+					// do not attempt a reconnect
+					if (log.isErrorEnabled()) {
+						log.error("Reconnection to {} failed after {} attempts. Giving up.", connectAddress, attempt - 1);
+					}
+					future.channel().eventLoop().submit(new Runnable() {
+						@Override
+						public void run() {
+							connections.accept(future.cause());
+						}
+					});
+					return;
+				}
+
+				attemptReconnect(tup);
+			} else {
+				// connected
+				if (log.isInfoEnabled()) {
+					log.info("CONNECTED: " + future.channel());
+				}
+
+				final Channel ioCh = future.channel();
+				final ChannelPipeline ioChPipline = ioCh.pipeline();
+				final NetChannel<IN, OUT> ch = ioChPipline.get(NettyNetChannelInboundHandler.class).getNetChannel();
+
+				ioChPipline.addLast(new ChannelDuplexHandler() {
+					@Override
+					public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+						if (log.isInfoEnabled()) {
+							log.info("CLOSED: " + ioCh);
+						}
+						notifyClose(ch);
+
+						Tuple2<InetSocketAddress, Long> tup = reconnect.reconnect(connectAddress, attempts.incrementAndGet());
+						if (null == tup) {
+							// do not attempt a reconnect
+							return;
+						}
+						if (!((NettyNetChannel) ch).isClosing()) {
+							attemptReconnect(tup);
+						} else {
+							closing = true;
+						}
+						super.channelInactive(ctx);
+					}
+				});
+
+				ioCh.eventLoop().submit(new Runnable() {
+					@Override
+					public void run() {
+						connections.accept(ch);
+					}
+				});
+			}
+		}
+
+		private void attemptReconnect(Tuple2<InetSocketAddress, Long> tup) {
+			connectAddress = tup.getT1();
+			bootstrap.remoteAddress(connectAddress);
+			long delay = tup.getT2();
+
+			if (log.isInfoEnabled()) {
+				log.info("Failed to connect to {}. Attempting reconnect in {}ms.", connectAddress, delay);
+			}
+
+			getEnvironment().getRootTimer()
+			                .submit(
+					                new Consumer<Long>() {
+						                @Override
+						                public void accept(Long now) {
+							                openChannel(ReconnectingChannelListener.this);
+						                }
+					                },
+					                delay,
+					                TimeUnit.MILLISECONDS
+			                )
+			                .cancelAfterUse();
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/netty/tcp/NettyTcpServer.java b/reactor-net/src/main/java/reactor/net/netty/tcp/NettyTcpServer.java
new file mode 100644
index 0000000..a671ce1
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/tcp/NettyTcpServer.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.netty.tcp;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.SocketChannelConfig;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.spec.Promises;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.AbstractNetChannel;
+import reactor.net.NetChannel;
+import reactor.net.config.ServerSocketOptions;
+import reactor.net.config.SslOptions;
+import reactor.net.netty.*;
+import reactor.net.tcp.TcpServer;
+import reactor.net.tcp.ssl.SSLEngineSupplier;
+import reactor.support.NamedDaemonThreadFactory;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLEngine;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A Netty-based {@code TcpServer} implementation
+ *
+ * @param <IN>
+ * 		The type that will be received by this server
+ * @param <OUT>
+ * 		The type that will be sent by this server
+ *
+ * @author Jon Brisbin
+ */
+public class NettyTcpServer<IN, OUT> extends TcpServer<IN, OUT> {
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+
+	private final NettyServerSocketOptions nettyOptions;
+	private final ServerBootstrap          bootstrap;
+	private final EventLoopGroup           selectorGroup;
+	private final EventLoopGroup           ioGroup;
+
+	protected NettyTcpServer(@Nonnull Environment env,
+	                         @Nonnull Reactor reactor,
+	                         @Nullable InetSocketAddress listenAddress,
+	                         final ServerSocketOptions options,
+	                         final SslOptions sslOptions,
+	                         @Nullable Codec<Buffer, IN, OUT> codec,
+	                         @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		super(env, reactor, listenAddress, options, sslOptions, codec, consumers);
+
+		if (options instanceof NettyServerSocketOptions) {
+			this.nettyOptions = (NettyServerSocketOptions) options;
+		} else {
+			this.nettyOptions = null;
+		}
+
+		int selectThreadCount = env.getProperty("reactor.tcp.selectThreadCount", Integer.class,
+		                                        Environment.PROCESSORS / 2);
+		int ioThreadCount = env.getProperty("reactor.tcp.ioThreadCount", Integer.class, Environment.PROCESSORS);
+		this.selectorGroup = new NioEventLoopGroup(selectThreadCount, new NamedDaemonThreadFactory("reactor-tcp-select"));
+		if (null != nettyOptions && null != nettyOptions.eventLoopGroup()) {
+			this.ioGroup = nettyOptions.eventLoopGroup();
+		} else {
+			this.ioGroup = new NioEventLoopGroup(ioThreadCount, new NamedDaemonThreadFactory("reactor-tcp-io"));
+		}
+
+		this.bootstrap = new ServerBootstrap()
+				.group(selectorGroup, ioGroup)
+				.channel(NioServerSocketChannel.class)
+				.option(ChannelOption.SO_BACKLOG, options.backlog())
+				.option(ChannelOption.SO_RCVBUF, options.rcvbuf())
+				.option(ChannelOption.SO_SNDBUF, options.sndbuf())
+				.option(ChannelOption.SO_REUSEADDR, options.reuseAddr())
+				.localAddress((null == listenAddress ? new InetSocketAddress(3000) : listenAddress))
+				.childHandler(new ChannelInitializer<SocketChannel>() {
+					@Override
+					public void initChannel(final SocketChannel ch) throws Exception {
+						SocketChannelConfig config = ch.config();
+						config.setReceiveBufferSize(options.rcvbuf());
+						config.setSendBufferSize(options.sndbuf());
+						config.setKeepAlive(options.keepAlive());
+						config.setReuseAddress(options.reuseAddr());
+						config.setSoLinger(options.linger());
+						config.setTcpNoDelay(options.tcpNoDelay());
+
+						if (log.isDebugEnabled()) {
+							log.debug("CONNECT {}", ch);
+						}
+
+						if (null != sslOptions) {
+							SSLEngine ssl = new SSLEngineSupplier(sslOptions, false).get();
+							if (log.isDebugEnabled()) {
+								log.debug("SSL enabled using keystore {}",
+								          (null != sslOptions.keystoreFile() ? sslOptions.keystoreFile() : "<DEFAULT>"));
+							}
+							ch.pipeline().addLast(new SslHandler(ssl));
+						}
+						if (null != nettyOptions && null != nettyOptions.pipelineConfigurer()) {
+							nettyOptions.pipelineConfigurer().accept(ch.pipeline());
+						}
+						ch.pipeline().addLast(createChannelHandlers(ch));
+						ch.closeFuture().addListener(new ChannelFutureListener() {
+							@Override
+							public void operationComplete(ChannelFuture future) throws Exception {
+								if (log.isDebugEnabled()) {
+									log.debug("CLOSE {}", ch);
+								}
+								close(ch);
+							}
+						});
+					}
+				});
+	}
+
+	@Override
+	public TcpServer<IN, OUT> start(@Nullable final Runnable started) {
+		ChannelFuture bindFuture = bootstrap.bind();
+		if (null != started) {
+			bindFuture.addListener(new ChannelFutureListener() {
+				@Override
+				public void operationComplete(ChannelFuture future) throws Exception {
+					log.info("BIND {}", future.channel().localAddress());
+					notifyStart(started);
+				}
+			});
+		}
+
+		return this;
+	}
+
+	@Override
+	public Promise<Boolean> shutdown() {
+		final Deferred<Boolean, Promise<Boolean>> d = Promises.defer(getEnvironment(), getReactor().getDispatcher());
+		getReactor().schedule(
+				new Consumer<Void>() {
+					@SuppressWarnings({"rawtypes", "unchecked"})
+					@Override
+					public void accept(Void v) {
+						final AtomicInteger groupsToShutdown = new AtomicInteger(2);
+						GenericFutureListener listener = new GenericFutureListener() {
+
+							@Override
+							public void operationComplete(Future future) throws Exception {
+								if (groupsToShutdown.decrementAndGet() == 0) {
+									notifyShutdown();
+									d.accept(true);
+								}
+							}
+						};
+						selectorGroup.shutdownGracefully().addListener(listener);
+						if (null == nettyOptions || null == nettyOptions.eventLoopGroup()) {
+							ioGroup.shutdownGracefully().addListener(listener);
+						}
+					}
+				},
+				null
+		);
+
+		return d.compose();
+	}
+
+	@Override
+	protected <C> NetChannel<IN, OUT> createChannel(C ioChannel) {
+		return new NettyNetChannel<IN, OUT>(
+				getEnvironment(),
+				getCodec(),
+				new NettyEventLoopDispatcher(((Channel) ioChannel).eventLoop(), 256),
+				getReactor(),
+				(Channel) ioChannel
+		);
+	}
+
+	protected ChannelHandler[] createChannelHandlers(SocketChannel ch) {
+		AbstractNetChannel<IN, OUT> netChannel = (AbstractNetChannel<IN, OUT>) select(ch);
+		NettyNetChannelInboundHandler readHandler = new NettyNetChannelInboundHandler()
+				.setNetChannel(netChannel);
+		NettyNetChannelOutboundHandler writeHandler = new NettyNetChannelOutboundHandler();
+
+		return new ChannelHandler[]{readHandler, writeHandler};
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/netty/udp/NettyDatagramServer.java b/reactor-net/src/main/java/reactor/net/netty/udp/NettyDatagramServer.java
new file mode 100644
index 0000000..6643d55
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/netty/udp/NettyDatagramServer.java
@@ -0,0 +1,302 @@
+package reactor.net.netty.udp;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.bootstrap.ChannelFactory;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannelConfig;
+import io.netty.channel.socket.DatagramPacket;
+import io.netty.channel.socket.nio.NioDatagramChannel;
+import io.netty.util.NetUtil;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.core.composable.spec.Promises;
+import reactor.event.dispatch.SynchronousDispatcher;
+import reactor.function.Consumer;
+import reactor.function.batch.BatchConsumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.NetChannel;
+import reactor.net.config.ServerSocketOptions;
+import reactor.net.netty.NettyNetChannel;
+import reactor.net.netty.NettyNetChannelInboundHandler;
+import reactor.net.netty.NettyServerSocketOptions;
+import reactor.net.udp.DatagramServer;
+import reactor.support.NamedDaemonThreadFactory;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.util.Collection;
+
+/**
+ * {@link reactor.net.udp.DatagramServer} implementation built on Netty.
+ *
+ * @author Jon Brisbin
+ */
+public class NettyDatagramServer<IN, OUT> extends DatagramServer<IN, OUT> {
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+
+	private final    NettyServerSocketOptions nettyOptions;
+	private final    Bootstrap                bootstrap;
+	private final    EventLoopGroup           ioGroup;
+	private volatile NioDatagramChannel       channel;
+	private volatile NettyNetChannel<IN, OUT> netChannel;
+
+	public NettyDatagramServer(@Nonnull Environment env,
+	                           @Nonnull Reactor reactor,
+	                           @Nullable InetSocketAddress listenAddress,
+	                           @Nullable final NetworkInterface multicastInterface,
+	                           @Nonnull final ServerSocketOptions options,
+	                           @Nullable Codec<Buffer, IN, OUT> codec,
+	                           Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		super(env, reactor, listenAddress, multicastInterface, options, codec, consumers);
+
+		if (options instanceof NettyServerSocketOptions) {
+			this.nettyOptions = (NettyServerSocketOptions) options;
+		} else {
+			this.nettyOptions = null;
+		}
+
+		if (null != nettyOptions && null != nettyOptions.eventLoopGroup()) {
+			this.ioGroup = nettyOptions.eventLoopGroup();
+		} else {
+			int ioThreadCount = env.getProperty("reactor.udp.ioThreadCount",
+			                                    Integer.class,
+			                                    Environment.PROCESSORS);
+			this.ioGroup = new NioEventLoopGroup(ioThreadCount, new NamedDaemonThreadFactory("reactor-udp-io"));
+		}
+
+		final NettyNetChannelInboundHandler inboundHandler = new NettyNetChannelInboundHandler() {
+			@Override
+			public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+				super.channelRead(ctx, ((DatagramPacket) msg).content());
+			}
+		};
+
+		this.bootstrap = new Bootstrap()
+				.group(ioGroup)
+				.option(ChannelOption.SO_RCVBUF, options.rcvbuf())
+				.option(ChannelOption.SO_SNDBUF, options.sndbuf())
+				.option(ChannelOption.SO_REUSEADDR, options.reuseAddr())
+				.channelFactory(new ChannelFactory<Channel>() {
+					@Override
+					public Channel newChannel() {
+						final NioDatagramChannel ch = new NioDatagramChannel();
+						DatagramChannelConfig config = ch.config();
+						config.setReceiveBufferSize(options.rcvbuf());
+						config.setSendBufferSize(options.sndbuf());
+						config.setReuseAddress(options.reuseAddr());
+
+						if (null != multicastInterface) {
+							config.setNetworkInterface(multicastInterface);
+						}
+
+						if (null != nettyOptions && null != nettyOptions.pipelineConfigurer()) {
+							nettyOptions.pipelineConfigurer().accept(ch.pipeline());
+						}
+
+						ch.closeFuture().addListener(new ChannelFutureListener() {
+							@Override
+							public void operationComplete(ChannelFuture future) throws Exception {
+								if (log.isInfoEnabled()) {
+									log.info("CLOSE {}", ch);
+								}
+								close(ch);
+							}
+						});
+
+						netChannel = (NettyNetChannel<IN, OUT>) select(ch);
+						inboundHandler.setNetChannel(netChannel);
+
+						ch.pipeline().addLast(new ChannelOutboundHandlerAdapter() {
+							@Override
+							public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+								super.write(ctx, msg, promise);
+							}
+						});
+
+						return ch;
+					}
+				})
+				.handler(inboundHandler);
+
+		if (null != listenAddress) {
+			bootstrap.localAddress(listenAddress);
+		} else {
+			bootstrap.localAddress(NetUtil.LOCALHOST, 3000);
+		}
+		if (null != multicastInterface) {
+			bootstrap.option(ChannelOption.IP_MULTICAST_IF, multicastInterface);
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public DatagramServer<IN, OUT> start(@Nullable final Runnable started) {
+		ChannelFuture future = bootstrap.bind();
+		if (null != started) {
+			future.addListener(new ChannelFutureListener() {
+				@Override
+				public void operationComplete(ChannelFuture future) throws Exception {
+					if (future.isSuccess()) {
+						log.info("BIND {}", future.channel().localAddress());
+						notifyStart(started);
+						channel = (NioDatagramChannel) future.channel();
+					}
+				}
+			});
+		}
+		return this;
+	}
+
+	@Override
+	public Promise<Boolean> shutdown() {
+		final Deferred<Boolean, Promise<Boolean>> d = Promises.defer(getEnvironment(), getReactor().getDispatcher());
+
+		getReactor().schedule(
+				new Consumer<Void>() {
+					@SuppressWarnings("unchecked")
+					@Override
+					public void accept(Void v) {
+						GenericFutureListener listener = new GenericFutureListener() {
+							@Override
+							public void operationComplete(Future future) throws Exception {
+								if (future.isSuccess()) {
+									d.accept(true);
+								} else {
+									d.accept(future.cause());
+								}
+							}
+						};
+						if (null == nettyOptions || null == nettyOptions.eventLoopGroup()) {
+							ioGroup.shutdownGracefully().addListener(listener);
+						}
+					}
+				},
+				null
+		);
+		notifyShutdown();
+
+		return d.compose();
+	}
+
+	@Override
+	public DatagramServer<IN, OUT> send(OUT data) {
+		if (null == channel) {
+			throw new IllegalStateException("DatagramServer not running.");
+		}
+
+		netChannel.send(data);
+
+		return this;
+	}
+
+	@Override
+	public Stream<IN> in() {
+		return netChannel.in();
+	}
+
+	@Override
+	public BatchConsumer<OUT> out() {
+		return netChannel.out();
+	}
+
+	@Override
+	public Promise<Void> join(InetAddress multicastAddress, NetworkInterface iface) {
+		if (null == channel) {
+			throw new IllegalStateException("DatagramServer not running.");
+		}
+
+		final Deferred<Void, Promise<Void>> d = Promises.defer(getEnvironment(), getReactor().getDispatcher());
+
+		if (null == iface && null != getMulticastInterface()) {
+			iface = getMulticastInterface();
+		}
+
+		final ChannelFuture future;
+		if (null != iface) {
+			future = channel.joinGroup(new InetSocketAddress(multicastAddress, getListenAddress().getPort()), iface);
+		} else {
+			future = channel.joinGroup(multicastAddress);
+		}
+		future.addListener(new PromiseCompletingListener(d));
+
+		return d.compose();
+	}
+
+	@Override
+	public Promise<Void> leave(InetAddress multicastAddress, NetworkInterface iface) {
+		if (null == channel) {
+			throw new IllegalStateException("DatagramServer not running.");
+		}
+
+		if (null == iface && null != getMulticastInterface()) {
+			iface = getMulticastInterface();
+		}
+
+		final Deferred<Void, Promise<Void>> d = Promises.defer(getEnvironment(), getReactor().getDispatcher());
+
+		final ChannelFuture future;
+		if (null != iface) {
+			future = channel.leaveGroup(new InetSocketAddress(multicastAddress, getListenAddress().getPort()), iface);
+		} else {
+			future = channel.leaveGroup(multicastAddress);
+		}
+		future.addListener(new PromiseCompletingListener(d));
+
+		return d.compose();
+	}
+
+	@Override
+	protected <C> NetChannel<IN, OUT> createChannel(C ioChannel) {
+		return new NettyNetChannel<IN, OUT>(
+				getEnvironment(),
+				getCodec(),
+				new SynchronousDispatcher(),
+				getReactor(),
+				(NioDatagramChannel) ioChannel
+		);
+	}
+
+	@Override
+	protected void doClose(@Nullable final Consumer<Boolean> onClose) {
+		ChannelFuture future = channel.close();
+		if (null != onClose) {
+			future.addListener(new ChannelFutureListener() {
+				@Override
+				public void operationComplete(ChannelFuture future) throws Exception {
+					getReactor().schedule(onClose, future.isDone() && future.isSuccess());
+				}
+			});
+		}
+	}
+
+	private static class PromiseCompletingListener implements ChannelFutureListener {
+		private final Deferred<Void, Promise<Void>> d;
+
+		private PromiseCompletingListener(Deferred<Void, Promise<Void>> d) {
+			this.d = d;
+		}
+
+		@Override
+		public void operationComplete(ChannelFuture future) throws Exception {
+			if (future.isSuccess()) {
+				d.accept((Void) null);
+			} else {
+				d.accept(future.cause());
+			}
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/spec/NetServerSpec.java b/reactor-net/src/main/java/reactor/net/spec/NetServerSpec.java
new file mode 100644
index 0000000..d4f98f6
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/spec/NetServerSpec.java
@@ -0,0 +1,150 @@
+package reactor.net.spec;
+
+import reactor.core.spec.support.EventRoutingComponentSpec;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.NetChannel;
+import reactor.net.NetServer;
+import reactor.net.config.ServerSocketOptions;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class NetServerSpec<IN, OUT, S extends NetServerSpec<IN, OUT, S, N>, N extends NetServer<IN, OUT>>
+		extends EventRoutingComponentSpec<S, N> {
+
+	protected ServerSocketOptions                       options          = new ServerSocketOptions();
+	protected Collection<Consumer<NetChannel<IN, OUT>>> channelConsumers = Collections.emptyList();
+	protected InetSocketAddress      listenAddress;
+	protected Codec<Buffer, IN, OUT> codec;
+
+	/**
+	 * Set the common {@link ServerSocketOptions} for channels made in this server.
+	 *
+	 * @param options
+	 * 		The options to set when new channels are made.
+	 *
+	 * @return {@literal this}
+	 */
+	@SuppressWarnings("unchecked")
+	public S options(@Nonnull ServerSocketOptions options) {
+		Assert.notNull(options, "ServerSocketOptions cannot be null.");
+		this.options = options;
+		return (S)this;
+	}
+
+	/**
+	 * The port on which this server should listen, assuming it should bind to all available addresses.
+	 *
+	 * @param port
+	 * 		The port to listen on.
+	 *
+	 * @return {@literal this}
+	 */
+	@SuppressWarnings("unchecked")
+	public S listen(int port) {
+		return listen(new InetSocketAddress(port));
+	}
+
+	/**
+	 * The host and port on which this server should listen.
+	 *
+	 * @param host
+	 * 		The host to bind to.
+	 * @param port
+	 * 		The port to listen on.
+	 *
+	 * @return {@literal this}
+	 */
+	@SuppressWarnings("unchecked")
+	public S listen(String host, int port) {
+		if(null == host) {
+			host = "localhost";
+		}
+		return listen(new InetSocketAddress(host, port));
+	}
+
+	/**
+	 * The {@link java.net.InetSocketAddress} on which this server should listen.
+	 *
+	 * @param listenAddress
+	 * 		the listen address
+	 *
+	 * @return {@literal this}
+	 */
+	@SuppressWarnings("unchecked")
+	public S listen(InetSocketAddress listenAddress) {
+		this.listenAddress = listenAddress;
+		return (S)this;
+	}
+
+	/**
+	 * The {@link Codec} to use to encode and decode data.
+	 *
+	 * @param codec
+	 * 		The codec to use.
+	 *
+	 * @return {@literal this}
+	 */
+	@SuppressWarnings("unchecked")
+	public S codec(@Nonnull Codec<Buffer, IN, OUT> codec) {
+		Assert.notNull(codec, "Codec cannot be null.");
+		this.codec = codec;
+		return (S)this;
+	}
+
+	/**
+	 * Callback to invoke when a new input message is created.
+	 *
+	 * @param inputConsumer
+	 * 		The callback to invoke for new messages.
+	 *
+	 * @return {@literal this}
+	 */
+	public S consumeInput(@Nonnull final Consumer<IN> inputConsumer) {
+		Collection<Consumer<NetChannel<IN, OUT>>> channelConsumers = Collections.<Consumer<NetChannel<IN,
+				OUT>>>singletonList(
+				new Consumer<NetChannel<IN, OUT>>() {
+					@Override
+					public void accept(NetChannel<IN, OUT> channel) {
+						channel.consume(inputConsumer);
+					}
+				});
+		return consume(channelConsumers);
+	}
+
+	/**
+	 * Callback to invoke when a new channel is created.
+	 *
+	 * @param channelConsumer
+	 * 		The callback to invoke for new channels.
+	 *
+	 * @return {@literal this}
+	 */
+	public S consume(@Nonnull Consumer<NetChannel<IN, OUT>> channelConsumer) {
+		return consume(Collections.singletonList(channelConsumer));
+	}
+
+	/**
+	 * Callbacks to invoke when a new channel is created.
+	 *
+	 * @param channelConsumers
+	 * 		The callbacks to invoke for new channels.
+	 *
+	 * @return {@literal this}
+	 */
+	@SuppressWarnings("unchecked")
+	public S consume(@Nonnull Collection<Consumer<NetChannel<IN, OUT>>> channelConsumers) {
+		Assert.notNull(channelConsumers, "Connection consumers cannot be null.");
+		this.channelConsumers = channelConsumers;
+		return (S)this;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/tcp/TcpClient.java b/reactor-net/src/main/java/reactor/net/tcp/TcpClient.java
new file mode 100644
index 0000000..2430479
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/TcpClient.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp;
+
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.AbstractNetPeer;
+import reactor.net.NetChannel;
+import reactor.net.NetClient;
+import reactor.net.Reconnect;
+import reactor.net.config.ClientSocketOptions;
+import reactor.net.config.SslOptions;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+
+/**
+ * The base class for a Reactor-based TCP client.
+ *
+ * @param <IN>
+ * 		The type that will be received by this client
+ * @param <OUT>
+ * 		The type that will be sent by this client
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public abstract class TcpClient<IN, OUT>
+		extends AbstractNetPeer<IN, OUT>
+		implements NetClient<IN, OUT> {
+
+	private final InetSocketAddress   connectAddress;
+	private final ClientSocketOptions options;
+	private final SslOptions          sslOptions;
+
+	protected TcpClient(@Nonnull Environment env,
+	                    @Nonnull Reactor reactor,
+	                    @Nullable InetSocketAddress connectAddress,
+	                    @Nullable ClientSocketOptions options,
+	                    @Nullable SslOptions sslOptions,
+	                    @Nullable Codec<Buffer, IN, OUT> codec,
+	                    @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		super(env, reactor, codec, consumers);
+		this.connectAddress = (null != connectAddress ? connectAddress : new InetSocketAddress("127.0.0.1", 3000));
+		this.options = options;
+		this.sslOptions = sslOptions;
+	}
+
+	/**
+	 * Open a {@link NetChannel} to the configured host:port and return a {@link reactor.core.composable.Promise} that
+	 * will be fulfilled when the client is connected.
+	 *
+	 * @return A {@link reactor.core.composable.Promise} that will be filled with the {@link NetChannel} when connected.
+	 */
+	public abstract Promise<NetChannel<IN, OUT>> open();
+
+	/**
+	 * Open a {@link NetChannel} to the configured host:port and return a {@link Stream} that will be passed a new {@link
+	 * NetChannel} object every time the client is connected to the endpoint. The given {@link reactor.net.Reconnect}
+	 * describes how the client should attempt to reconnect to the host if the initial connection fails or if the client
+	 * successfully connects but at some point in the future gets cut off from the host. The {@code Reconnect} tells the
+	 * client where to try reconnecting and gives a delay describing how long to wait to attempt to reconnect. When the
+	 * connect is successfully made, the {@link Stream} is sent a new {@link NetChannel} backed by the newly-connected
+	 * connection.
+	 *
+	 * @param reconnect
+	 *
+	 * @return
+	 */
+	public abstract Stream<NetChannel<IN, OUT>> open(Reconnect reconnect);
+
+	/**
+	 * Get the {@link java.net.InetSocketAddress} to which this client must connect.
+	 *
+	 * @return the connect address
+	 */
+	public InetSocketAddress getConnectAddress() {
+		return connectAddress;
+	}
+
+	/**
+	 * Get the {@link reactor.net.config.ClientSocketOptions} currently in effect.
+	 *
+	 * @return the client options
+	 */
+	protected ClientSocketOptions getOptions() {
+		return this.options;
+	}
+
+	/**
+	 * Get the {@link reactor.net.config.SslOptions} current in effect.
+	 *
+	 * @return the SSL options
+	 */
+	protected SslOptions getSslOptions() {
+		return sslOptions;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/tcp/TcpServer.java b/reactor-net/src/main/java/reactor/net/tcp/TcpServer.java
new file mode 100644
index 0000000..6309194
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/TcpServer.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp;
+
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.spec.Promises;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.AbstractNetPeer;
+import reactor.net.NetChannel;
+import reactor.net.NetServer;
+import reactor.net.config.ServerSocketOptions;
+import reactor.net.config.SslOptions;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+
+/**
+ * Base functionality needed by all servers that communicate with clients over TCP.
+ *
+ * @param <IN>
+ * 		The type that will be received by this server
+ * @param <OUT>
+ * 		The type that will be sent by this server
+ *
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public abstract class TcpServer<IN, OUT>
+		extends AbstractNetPeer<IN, OUT>
+		implements NetServer<IN, OUT> {
+
+	private final InetSocketAddress   listenAddress;
+	private final ServerSocketOptions options;
+	private final SslOptions          sslOptions;
+
+	protected TcpServer(@Nonnull Environment env,
+	                    @Nonnull Reactor reactor,
+	                    @Nullable InetSocketAddress listenAddress,
+	                    ServerSocketOptions options,
+	                    SslOptions sslOptions,
+	                    @Nullable Codec<Buffer, IN, OUT> codec,
+	                    @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		super(env, reactor, codec, consumers);
+		this.listenAddress = listenAddress;
+		Assert.notNull(options, "ServerSocketOptions cannot be null");
+		this.options = options;
+		this.sslOptions = sslOptions;
+	}
+
+	/**
+	 * Start this server.
+	 *
+	 * @return {@literal this}
+	 */
+	public Promise<Boolean> start() {
+		final Deferred<Boolean, Promise<Boolean>> d = Promises.defer(getEnvironment(), getReactor().getDispatcher());
+		start(new Runnable() {
+			@Override
+			public void run() {
+				d.accept(true);
+			}
+		});
+		return d.compose();
+	}
+
+	/**
+	 * Start this server, invoking the given callback when the server has started.
+	 *
+	 * @param started
+	 * 		Callback to invoke when the server is started. May be {@literal null}.
+	 *
+	 * @return {@literal this}
+	 */
+	public abstract TcpServer<IN, OUT> start(@Nullable Runnable started);
+
+	/**
+	 * Get the address to which this server is bound.
+	 *
+	 * @return
+	 */
+	protected InetSocketAddress getListenAddress() {
+		return listenAddress;
+	}
+
+	/**
+	 * Get the {@link reactor.net.config.ServerSocketOptions} currently in effect.
+	 *
+	 * @return
+	 */
+	protected ServerSocketOptions getOptions() {
+		return options;
+	}
+
+	/**
+	 * Get the {@link reactor.net.config.SslOptions} current in effect.
+	 *
+	 * @return the SSL options
+	 */
+	protected SslOptions getSslOptions() {
+		return sslOptions;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/tcp/package-info.java b/reactor-net/src/main/java/reactor/net/tcp/package-info.java
new file mode 100644
index 0000000..e846884
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Components for writing TCP-based clients and servers using Reactor abstractions.
+ */
+package reactor.net.tcp;
\ No newline at end of file
diff --git a/reactor-net/src/main/java/reactor/net/tcp/spec/IncrementalBackoffReconnectSpec.java b/reactor-net/src/main/java/reactor/net/tcp/spec/IncrementalBackoffReconnectSpec.java
new file mode 100644
index 0000000..f188f94
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/spec/IncrementalBackoffReconnectSpec.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.spec;
+
+import reactor.function.Supplier;
+import reactor.function.Suppliers;
+import reactor.net.Reconnect;
+import reactor.tuple.Tuple;
+import reactor.tuple.Tuple2;
+
+import java.net.InetSocketAddress;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A helper class for configure a new {@code Reconnect}.
+ *
+ */
+public class IncrementalBackoffReconnectSpec implements Supplier<Reconnect> {
+    public static final long DEFAULT_INTERVAL     = 5000;
+    public static final long DEFAULT_MULTIPLIER   = 1;
+    public static final long DEFAULT_MAX_ATTEMPTS = -1;
+
+    private final List<InetSocketAddress> addresses;
+    private long interval;
+    private long multiplier;
+    private long maxInterval;
+    private long maxAttempts;
+
+    /**
+     *
+     */
+    public IncrementalBackoffReconnectSpec() {
+        this.addresses   = new LinkedList<InetSocketAddress>();
+        this.interval    = DEFAULT_INTERVAL;
+        this.multiplier  = DEFAULT_MULTIPLIER;
+        this.maxInterval = Long.MAX_VALUE;
+        this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
+    }
+
+    /**
+     * Set the reconnection interval.
+     *
+     * @param interval the period reactor waits between attemps to reconnect disconnected peers
+     * @return {@literal this}
+     */
+    public IncrementalBackoffReconnectSpec interval(long interval) {
+        this.interval = interval;
+        return this;
+    }
+
+    /**
+     * Set the maximum reconnection interval that will be applied if the multiplier
+     * is set to a value greather than one.
+     *
+     * @param maxInterval
+     * @return {@literal this}
+     */
+    public IncrementalBackoffReconnectSpec maxInterval(long maxInterval) {
+        this.maxInterval = maxInterval;
+        return this;
+    }
+
+    /**
+     * Set the backoff multiplier.
+     *
+     * @param multiplier
+     * @return {@literal this}
+     */
+    public IncrementalBackoffReconnectSpec multiplier(long multiplier) {
+        this.multiplier = multiplier;
+        return this;
+    }
+
+    /**
+     * Sets the number of time that Reactor will attempt to connect or reconnect
+     * before giving up.
+     *
+     * @param maxAttempts The max number of attempts made before failing.
+     * @return {@literal this}
+     */
+    public IncrementalBackoffReconnectSpec maxAttempts(long maxAttempts) {
+        this.maxAttempts = maxAttempts;
+        return this;
+    }
+
+    /**
+     * Add an address to the pool of addresses.
+     *
+     * @param address
+     * @return {@literal this}
+     */
+    public IncrementalBackoffReconnectSpec address(InetSocketAddress address) {
+        this.addresses.add(address);
+        return this;
+    }
+
+    /**
+     * Add an address to the pool of addresses.
+     *
+     * @param host
+     * @param port
+     * @return {@literal this}
+     */
+    public IncrementalBackoffReconnectSpec address(String host, int port) {
+        this.addresses.add(new InetSocketAddress(host,port));
+        return this;
+    }
+
+
+    @Override
+    public Reconnect get() {
+        final Supplier<InetSocketAddress> endpoints =
+            Suppliers.roundRobin(addresses.toArray(new InetSocketAddress[]{}));
+
+        return new Reconnect() {
+            public Tuple2<InetSocketAddress, Long> reconnect(InetSocketAddress currentAddress, int attempt) {
+                Tuple2<InetSocketAddress, Long> rv = null;
+                synchronized(IncrementalBackoffReconnectSpec.this) {
+                    if(!addresses.isEmpty()) {
+                        if(IncrementalBackoffReconnectSpec.this.maxAttempts == -1       ||
+                           IncrementalBackoffReconnectSpec.this.maxAttempts > attempt ) {
+                            rv = Tuple.of(endpoints.get(),determineInterval(attempt));
+                        }
+                    } else {
+                        rv = Tuple.of(currentAddress,determineInterval(attempt));
+                    }
+                }
+
+                return rv;
+            }
+        };
+    }
+
+    /**
+     * Determine the period in milliseconds between reconnection attempts.
+     *
+     * @param attempt the number of times a reconnection has been attempted
+     * @return the reconnection period
+     */
+    public long determineInterval(int attempt) {
+        return (multiplier > 1) ? Math.min(maxInterval,interval * attempt) : interval;
+    }
+}
+
diff --git a/reactor-net/src/main/java/reactor/net/tcp/spec/TcpClientSpec.java b/reactor-net/src/main/java/reactor/net/tcp/spec/TcpClientSpec.java
new file mode 100644
index 0000000..bb6b6f1
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/spec/TcpClientSpec.java
@@ -0,0 +1,189 @@
+package reactor.net.tcp.spec;
+
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.spec.support.EventRoutingComponentSpec;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.NetChannel;
+import reactor.net.config.ClientSocketOptions;
+import reactor.net.config.SslOptions;
+import reactor.net.tcp.TcpClient;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.lang.reflect.Constructor;
+import java.net.InetSocketAddress;
+import java.util.*;
+
+/**
+ * A helper class for specifying a {@code TcpClient}
+ *
+ * @param <IN>
+ * 		The type that will be received by the client
+ * @param <OUT>
+ * 		The type that will be sent by the client
+ *
+ * @author Jon Brisbin
+ */
+public class TcpClientSpec<IN, OUT> extends EventRoutingComponentSpec<TcpClientSpec<IN, OUT>, TcpClient<IN, OUT>> {
+
+	private final Constructor<TcpClient<IN, OUT>> clientImplConstructor;
+
+	private InetSocketAddress connectAddress;
+	private ClientSocketOptions      options    = new ClientSocketOptions();
+	private Collection<Consumer<IN>> consumers  = Collections.emptyList();
+	private SslOptions               sslOptions = null;
+	private Codec<Buffer, IN, OUT> codec;
+
+	/**
+	 * Create a {@code TcpClient.Spec} using the given implementation class.
+	 *
+	 * @param clientImpl
+	 * 		The concrete implementation of {@link TcpClient} to instantiate.
+	 */
+	@SuppressWarnings({"unchecked", "rawtypes"})
+	public TcpClientSpec(@Nonnull Class<? extends TcpClient> clientImpl) {
+		Assert.notNull(clientImpl, "TcpClient implementation class cannot be null.");
+		try {
+			this.clientImplConstructor = (Constructor<TcpClient<IN, OUT>>)clientImpl.getDeclaredConstructor(
+					Environment.class,
+					Reactor.class,
+					InetSocketAddress.class,
+					ClientSocketOptions.class,
+					SslOptions.class,
+					Codec.class,
+					Collection.class
+			);
+			this.clientImplConstructor.setAccessible(true);
+		} catch(NoSuchMethodException e) {
+			throw new IllegalArgumentException(
+					"No public constructor found that matches the signature of the one found in the TcpClient class.");
+		}
+	}
+
+	/**
+	 * Set the common {@link ClientSocketOptions} for connections made in this client.
+	 *
+	 * @param options
+	 * 		The socket options to apply to new connections.
+	 *
+	 * @return {@literal this}
+	 */
+	public TcpClientSpec<IN, OUT> options(ClientSocketOptions options) {
+		this.options = options;
+		return this;
+	}
+
+	/**
+	 * Set the options to use for configuring SSL. Setting this to {@code null} means don't use SSL at all (the default).
+	 *
+	 * @param sslOptions
+	 * 		The options to set when configuring SSL
+	 *
+	 * @return {@literal this}
+	 */
+	public TcpClientSpec<IN, OUT> ssl(@Nullable SslOptions sslOptions) {
+		this.sslOptions = sslOptions;
+		return this;
+	}
+
+	/**
+	 * The host and port to which this client should connect.
+	 *
+	 * @param host
+	 * 		The host to connect to.
+	 * @param port
+	 * 		The port to connect to.
+	 *
+	 * @return {@literal this}
+	 */
+	public TcpClientSpec<IN, OUT> connect(@Nonnull String host, int port) {
+		return connect(new InetSocketAddress(host, port));
+	}
+
+	/**
+	 * The address to which this client should connect.
+	 *
+	 * @param connectAddress
+	 * 		The address to connect to.
+	 *
+	 * @return {@literal this}
+	 */
+	public TcpClientSpec<IN, OUT> connect(@Nonnull InetSocketAddress connectAddress) {
+		Assert.isNull(this.connectAddress, "Connect address is already set.");
+		this.connectAddress = connectAddress;
+		return this;
+	}
+
+	/**
+	 * The {@link Codec} to use to encode and decode data.
+	 *
+	 * @param codec
+	 * 		The codec to use.
+	 *
+	 * @return {@literal this}
+	 */
+	public TcpClientSpec<IN, OUT> codec(@Nullable Codec<Buffer, IN, OUT> codec) {
+		Assert.isNull(this.codec, "Codec has already been set.");
+		this.codec = codec;
+		return this;
+	}
+
+	/**
+	 * The {@link reactor.function.Consumer} to use to ingest incoming data.
+	 *
+	 * @param consumer
+	 * 		the incoming data {@link reactor.function.Consumer}
+	 *
+	 * @return {@literal this}
+	 */
+	@SuppressWarnings("unchecked")
+	public TcpClientSpec<IN, OUT> consume(Consumer<IN> consumer) {
+		consume(Arrays.asList(consumer));
+		return this;
+	}
+
+	/**
+	 * The {@link reactor.function.Consumer Consumers} to use to ingest the incoming data.
+	 *
+	 * @param consumers
+	 * 		the incoming data {@link reactor.function.Consumer Consumers}
+	 *
+	 * @return {@literal this}
+	 */
+	public TcpClientSpec<IN, OUT> consume(Collection<Consumer<IN>> consumers) {
+		Assert.notNull(consumers, "Consumers cannot be null");
+		this.consumers = consumers;
+		return this;
+	}
+
+	@Override
+	protected TcpClient<IN, OUT> configure(Reactor reactor, Environment env) {
+		List<Consumer<NetChannel<IN, OUT>>> channelConsumers = new ArrayList<Consumer<NetChannel<IN, OUT>>>();
+		channelConsumers.add(new Consumer<NetChannel<IN, OUT>>() {
+			@Override
+			public void accept(NetChannel<IN, OUT> netChannel) {
+				for(Consumer<IN> consumer : consumers) {
+					netChannel.consume(consumer);
+				}
+			}
+		});
+		try {
+			return clientImplConstructor.newInstance(
+					env,
+					reactor,
+					connectAddress,
+					options,
+					sslOptions,
+					codec,
+					channelConsumers
+			);
+		} catch(Throwable t) {
+			throw new IllegalStateException(t);
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/tcp/spec/TcpClients.java b/reactor-net/src/main/java/reactor/net/tcp/spec/TcpClients.java
new file mode 100644
index 0000000..eb1a122
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/spec/TcpClients.java
@@ -0,0 +1,58 @@
+package reactor.net.tcp.spec;
+
+import reactor.core.Environment;
+import reactor.net.tcp.TcpClient;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Helper class to make creating {@link reactor.net.tcp.TcpClient} instances more succinct.
+ *
+ * @author Jon Brisbin
+ */
+public class TcpClients {
+
+	/**
+	 * Create a {@link reactor.net.tcp.spec.TcpClientSpec} for further configuration using the given {@link
+	 * reactor.core.Environment} and {@code clientImpl}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param clientImpl
+	 * 		the {@link reactor.net.tcp.TcpClient} implementation to use
+	 * @param <IN>
+	 * 		type of the input
+	 * @param <OUT>
+	 * 		type of the output
+	 *
+	 * @return a new {@link reactor.net.tcp.spec.TcpClientSpec}
+	 */
+	public static <IN, OUT> TcpClientSpec<IN, OUT> create(Environment env,
+	                                                      @Nonnull Class<? extends TcpClient> clientImpl) {
+		return new TcpClientSpec<IN, OUT>(clientImpl).env(env);
+	}
+
+	/**
+	 * Create a {@link reactor.net.tcp.spec.TcpClientSpec} for further configuration using the given {@link
+	 * reactor.core.Environment}, {@link reactor.event.dispatch.Dispatcher} type, and {@code clientImpl}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param dispatcher
+	 * 		the type of {@link reactor.event.dispatch.Dispatcher} to use
+	 * @param clientImpl
+	 * 		the {@link reactor.net.tcp.TcpClient} implementation to use
+	 * @param <IN>
+	 * 		type of the input
+	 * @param <OUT>
+	 * 		type of the output
+	 *
+	 * @return a new {@link reactor.net.tcp.spec.TcpClientSpec}
+	 */
+	public static <IN, OUT> TcpClientSpec<IN, OUT> create(Environment env,
+	                                                      String dispatcher,
+	                                                      @Nonnull Class<? extends TcpClient> clientImpl) {
+		return new TcpClientSpec<IN, OUT>(clientImpl).env(env).dispatcher(dispatcher);
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/tcp/spec/TcpServerSpec.java b/reactor-net/src/main/java/reactor/net/tcp/spec/TcpServerSpec.java
new file mode 100644
index 0000000..4763aa5
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/spec/TcpServerSpec.java
@@ -0,0 +1,92 @@
+package reactor.net.tcp.spec;
+
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.io.encoding.Codec;
+import reactor.net.config.ServerSocketOptions;
+import reactor.net.config.SslOptions;
+import reactor.net.spec.NetServerSpec;
+import reactor.net.tcp.TcpServer;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.lang.reflect.Constructor;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+
+/**
+ * A TcpServerSpec is used to specify a TcpServer
+ *
+ * @param <IN>
+ * 		The type that will be received by this client
+ * @param <OUT>
+ * 		The type that will be sent by this client
+ *
+ * @author Jon Brisbin
+ */
+public class TcpServerSpec<IN, OUT>
+		extends NetServerSpec<IN, OUT, TcpServerSpec<IN, OUT>, TcpServer<IN, OUT>> {
+
+	private final Constructor<? extends TcpServer> serverImplConstructor;
+
+	private SslOptions sslOptions = null;
+
+	/**
+	 * Create a {@code TcpServer.Spec} using the given implementation class.
+	 *
+	 * @param serverImpl
+	 * 		The concrete implementation of {@link TcpServer} to instantiate.
+	 */
+	@SuppressWarnings({"unchecked", "rawtypes"})
+	public TcpServerSpec(@Nonnull Class<? extends TcpServer> serverImpl) {
+		Assert.notNull(serverImpl, "TcpServer implementation class cannot be null.");
+		try {
+			this.serverImplConstructor = serverImpl.getDeclaredConstructor(
+					Environment.class,
+					Reactor.class,
+					InetSocketAddress.class,
+					ServerSocketOptions.class,
+					SslOptions.class,
+					Codec.class,
+					Collection.class
+			);
+			this.serverImplConstructor.setAccessible(true);
+		} catch(NoSuchMethodException e) {
+			throw new IllegalArgumentException(
+					"No public constructor found that matches the signature of the one found in the TcpServer class.");
+		}
+	}
+
+	/**
+	 * Set the options to use for configuring SSL. Setting this to {@code null} means don't use SSL at all (the default).
+	 *
+	 * @param sslOptions
+	 * 		The options to set when configuring SSL
+	 *
+	 * @return {@literal this}
+	 */
+	public TcpServerSpec<IN, OUT> ssl(@Nullable SslOptions sslOptions) {
+		this.sslOptions = sslOptions;
+		return this;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected TcpServer<IN, OUT> configure(Reactor reactor, Environment env) {
+		try {
+			return serverImplConstructor.newInstance(
+					env,
+					reactor,
+					listenAddress,
+					options,
+					sslOptions,
+					codec,
+					channelConsumers
+			);
+		} catch(Throwable t) {
+			throw new IllegalStateException(t);
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/tcp/spec/TcpServers.java b/reactor-net/src/main/java/reactor/net/tcp/spec/TcpServers.java
new file mode 100644
index 0000000..904c9f9
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/spec/TcpServers.java
@@ -0,0 +1,61 @@
+package reactor.net.tcp.spec;
+
+import reactor.core.Environment;
+import reactor.net.tcp.TcpServer;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Helper class to make creating {@link reactor.net.tcp.TcpServer} instances more succinct.
+ *
+ * @author Jon Brisbin
+ */
+public abstract class TcpServers {
+
+	protected TcpServers() {
+	}
+
+	/**
+	 * Create a {@link reactor.net.tcp.spec.TcpServerSpec} for further configuration using the given {@link
+	 * reactor.core.Environment} and {@code serverImpl}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param serverImpl
+	 * 		the implementation of {@link reactor.net.tcp.TcpServer}
+	 * @param <IN>
+	 * 		type of the input
+	 * @param <OUT>
+	 * 		type of the output
+	 *
+	 * @return a {@link reactor.net.tcp.spec.TcpServerSpec} to be further configured
+	 */
+	public static <IN, OUT> TcpServerSpec<IN, OUT> create(Environment env,
+	                                                      @Nonnull Class<? extends TcpServer> serverImpl) {
+		return new TcpServerSpec<IN, OUT>(serverImpl).env(env);
+	}
+
+	/**
+	 * Create a {@link reactor.net.tcp.spec.TcpServerSpec} for further configuration using the given {@link
+	 * reactor.core.Environment} and {@code serverImpl}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param dispatcher
+	 * 		the type of dispatcher to use
+	 * @param serverImpl
+	 * 		the implementation of {@link reactor.net.tcp.TcpServer}
+	 * @param <IN>
+	 * 		type of the input
+	 * @param <OUT>
+	 * 		type of the output
+	 *
+	 * @return a {@link reactor.net.tcp.spec.TcpServerSpec} to be further configured
+	 */
+	public static <IN, OUT> TcpServerSpec<IN, OUT> create(Environment env,
+	                                                      String dispatcher,
+	                                                      @Nonnull Class<? extends TcpServer> serverImpl) {
+		return new TcpServerSpec<IN, OUT>(serverImpl).env(env).dispatcher(dispatcher);
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/tcp/spec/package-info.java b/reactor-net/src/main/java/reactor/net/tcp/spec/package-info.java
new file mode 100644
index 0000000..8d3e207
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/spec/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Specs provide a fluent DSL for building {@link reactor.net.tcp.TcpClient TcpClients} and {@link reactor.net.tcp.TcpServer
+ * TcpServers} using common configuration options.
+ */
+package reactor.net.tcp.spec;
\ No newline at end of file
diff --git a/reactor-net/src/main/java/reactor/net/tcp/ssl/SSLEngineSupplier.java b/reactor-net/src/main/java/reactor/net/tcp/ssl/SSLEngineSupplier.java
new file mode 100644
index 0000000..9dce2a1
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/ssl/SSLEngineSupplier.java
@@ -0,0 +1,54 @@
+package reactor.net.tcp.ssl;
+
+import reactor.function.Supplier;
+import reactor.net.config.SslOptions;
+
+import javax.net.ssl.*;
+import java.io.FileInputStream;
+import java.security.KeyStore;
+
+/**
+ * @author Jon Brisbin
+ */
+public class SSLEngineSupplier implements Supplier<SSLEngine> {
+
+	private final SSLEngine ssl;
+
+	public SSLEngineSupplier(SslOptions sslOpts, boolean client) throws Exception {
+		if (null == sslOpts || null == sslOpts.keystoreFile()) {
+			ssl = SSLContext.getDefault().createSSLEngine();
+			ssl.setUseClientMode(client);
+			return;
+		}
+
+		KeyStore ks = KeyStore.getInstance("JKS");
+		FileInputStream ksin = new FileInputStream(sslOpts.keystoreFile());
+		ks.load(ksin, sslOpts.keystorePasswd().toCharArray());
+
+		SSLContext ctx = SSLContext.getInstance(sslOpts.sslProtocol());
+		KeyManager[] keyManagers;
+		TrustManager[] trustManagers;
+		if (null != sslOpts.trustManagers()) {
+			trustManagers = sslOpts.trustManagers().get();
+		} else {
+			TrustManagerFactory tmf = TrustManagerFactory.getInstance(sslOpts.trustManagerFactoryAlgorithm());
+			tmf.init(ks);
+			trustManagers = tmf.getTrustManagers();
+		}
+
+		KeyManagerFactory kmf = KeyManagerFactory.getInstance(sslOpts.keyManagerFactoryAlgorithm());
+		kmf.init(ks, (null != sslOpts.keyManagerPasswd() ? sslOpts.keyManagerPasswd() : sslOpts.keystorePasswd()).toCharArray());
+		keyManagers = kmf.getKeyManagers();
+
+		ctx.init(keyManagers, trustManagers, null);
+
+		ssl = ctx.createSSLEngine();
+		ssl.setUseClientMode(client);
+	}
+
+	@Override
+	public SSLEngine get() {
+		return ssl;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/tcp/ssl/package-info.java b/reactor-net/src/main/java/reactor/net/tcp/ssl/package-info.java
new file mode 100644
index 0000000..6454f83
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/ssl/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Support classes for Reactor's TCP SSL support.
+ */
+package reactor.net.tcp.ssl;
\ No newline at end of file
diff --git a/reactor-net/src/main/java/reactor/net/tcp/support/SocketUtils.java b/reactor-net/src/main/java/reactor/net/tcp/support/SocketUtils.java
new file mode 100644
index 0000000..456d2c8
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/tcp/support/SocketUtils.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package reactor.net.tcp.support;
+
+import reactor.util.Assert;
+
+import javax.net.ServerSocketFactory;
+import java.net.DatagramSocket;
+import java.net.ServerSocket;
+import java.util.Random;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * NOTE: This code is a copy of that available in the Spring Framework.
+ *
+ * <p>Simple utility methods for working with network sockets — for example,
+ * for finding available ports on {@code localhost}.</p>
+ *
+ * <p>Within this class, a TCP port refers to a port for a {@link ServerSocket};
+ * whereas, a UDP port refers to a port for a {@link DatagramSocket}.</p>
+ *
+ * @author Sam Brannen
+ * @author Ben Hale
+ * @author Arjen Poutsma
+ * @author Gunnar Hillert
+ * @see <a href="http://spring.io">Borrowed from the Spring Framework</a>
+ * */
+public final class SocketUtils {
+
+	/**
+	 * The default minimum value for port ranges used when finding an available
+	 * socket port.
+	 */
+	public static final int PORT_RANGE_MIN = 1024;
+
+	/**
+	 * The default maximum value for port ranges used when finding an available
+	 * socket port.
+	 */
+	public static final int PORT_RANGE_MAX = 65535;
+
+	private static final Random random = new Random(System.currentTimeMillis());
+
+
+	/**
+	 * Although {@code SocketUtils} consists solely of static utility methods,
+	 * this constructor is intentionally {@code public}.
+	 *
+	 * <h4>Rationale</h4>
+	 *
+	 * <p>Static methods from this class may be invoked from within XML
+	 * configuration files using the Spring Expression Language (SpEL) and the
+	 * following syntax.
+	 *
+	 * <pre><code><bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}"
+	 * /></code></pre>
+	 *
+	 * If this constructor were {@code private}, you would be required to supply
+	 * the fully qualified class name to SpEL's {@code T()} function for each usage.
+	 * Thus, the fact that this constructor is {@code public} allows you to reduce
+	 * boilerplate configuration with SpEL as can be seen in the following example.
+	 *
+	 * <pre><code><bean id="socketUtils" class="org.springframework.util.SocketUtils" />
+	 *
+	 * <bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" />
+	 *
+	 * <bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" /></code></pre>
+	 */
+	public SocketUtils() {
+		/* no-op */
+	}
+
+	/**
+	 * Find an available TCP port randomly selected from the range
+	 * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
+	 *
+	 * @return an available TCP port number
+	 *
+	 * @throws IllegalStateException
+	 * 		if no available port could be found
+	 */
+	public static int findAvailableTcpPort() {
+		return findAvailableTcpPort(PORT_RANGE_MIN);
+	}
+
+	/**
+	 * Find an available TCP port randomly selected from the range
+	 * [{@code minPort}, {@value #PORT_RANGE_MAX}].
+	 *
+	 * @param minPort
+	 * 		the minimum port number
+	 *
+	 * @return an available TCP port number
+	 *
+	 * @throws IllegalStateException
+	 * 		if no available port could be found
+	 */
+	public static int findAvailableTcpPort(int minPort) {
+		return findAvailableTcpPort(minPort, PORT_RANGE_MAX);
+	}
+
+	/**
+	 * Find an available TCP port randomly selected from the range
+	 * [{@code minPort}, {@code maxPort}].
+	 *
+	 * @param minPort
+	 * 		the minimum port number
+	 * @param maxPort
+	 * 		the maximum port number
+	 *
+	 * @return an available TCP port number
+	 *
+	 * @throws IllegalStateException
+	 * 		if no available port could be found
+	 */
+	public static int findAvailableTcpPort(int minPort, int maxPort) {
+		return SocketType.TCP.findAvailablePort(minPort, maxPort);
+	}
+
+	/**
+	 * Find the requested number of available TCP ports, each randomly selected
+	 * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
+	 *
+	 * @param numRequested
+	 * 		the number of available ports to find
+	 *
+	 * @return a sorted set of available TCP port numbers
+	 *
+	 * @throws IllegalStateException
+	 * 		if the requested number of available ports could not be found
+	 */
+	public static SortedSet<Integer> findAvailableTcpPorts(int numRequested) {
+		return findAvailableTcpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX);
+	}
+
+	/**
+	 * Find the requested number of available TCP ports, each randomly selected
+	 * from the range [{@code minPort}, {@code maxPort}].
+	 *
+	 * @param numRequested
+	 * 		the number of available ports to find
+	 * @param minPort
+	 * 		the minimum port number
+	 * @param maxPort
+	 * 		the maximum port number
+	 *
+	 * @return a sorted set of available TCP port numbers
+	 *
+	 * @throws IllegalStateException
+	 * 		if the requested number of available ports could not be found
+	 */
+	public static SortedSet<Integer> findAvailableTcpPorts(int numRequested, int minPort, int maxPort) {
+		return SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort);
+	}
+
+	/**
+	 * Find an available UDP port randomly selected from the range
+	 * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
+	 *
+	 * @return an available UDP port number
+	 *
+	 * @throws IllegalStateException
+	 * 		if no available port could be found
+	 */
+	public static int findAvailableUdpPort() {
+		return findAvailableUdpPort(PORT_RANGE_MIN);
+	}
+
+	/**
+	 * Find an available UDP port randomly selected from the range
+	 * [{@code minPort}, {@value #PORT_RANGE_MAX}].
+	 *
+	 * @param minPort
+	 * 		the minimum port number
+	 *
+	 * @return an available UDP port number
+	 *
+	 * @throws IllegalStateException
+	 * 		if no available port could be found
+	 */
+	public static int findAvailableUdpPort(int minPort) {
+		return findAvailableUdpPort(minPort, PORT_RANGE_MAX);
+	}
+
+	/**
+	 * Find an available UDP port randomly selected from the range
+	 * [{@code minPort}, {@code maxPort}].
+	 *
+	 * @param minPort
+	 * 		the minimum port number
+	 * @param maxPort
+	 * 		the maximum port number
+	 *
+	 * @return an available UDP port number
+	 *
+	 * @throws IllegalStateException
+	 * 		if no available port could be found
+	 */
+	public static int findAvailableUdpPort(int minPort, int maxPort) {
+		return SocketType.UDP.findAvailablePort(minPort, maxPort);
+	}
+
+	/**
+	 * Find the requested number of available UDP ports, each randomly selected
+	 * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
+	 *
+	 * @param numRequested
+	 * 		the number of available ports to find
+	 *
+	 * @return a sorted set of available UDP port numbers
+	 *
+	 * @throws IllegalStateException
+	 * 		if the requested number of available ports could not be found
+	 */
+	public static SortedSet<Integer> findAvailableUdpPorts(int numRequested) {
+		return findAvailableUdpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX);
+	}
+
+	/**
+	 * Find the requested number of available UDP ports, each randomly selected
+	 * from the range [{@code minPort}, {@code maxPort}].
+	 *
+	 * @param numRequested
+	 * 		the number of available ports to find
+	 * @param minPort
+	 * 		the minimum port number
+	 * @param maxPort
+	 * 		the maximum port number
+	 *
+	 * @return a sorted set of available UDP port numbers
+	 *
+	 * @throws IllegalStateException
+	 * 		if the requested number of available ports could not be found
+	 */
+	public static SortedSet<Integer> findAvailableUdpPorts(int numRequested, int minPort, int maxPort) {
+		return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort);
+	}
+
+
+	private static enum SocketType {
+
+		TCP {
+			@Override
+			protected boolean isPortAvailable(int port) {
+				try {
+					ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port);
+					serverSocket.close();
+					return true;
+				} catch(Exception ex) {
+					return false;
+				}
+			}
+		},
+
+		UDP {
+			@Override
+			protected boolean isPortAvailable(int port) {
+				try {
+					DatagramSocket socket = new DatagramSocket(port);
+					socket.close();
+					return true;
+				} catch(Exception ex) {
+					return false;
+				}
+			}
+		};
+
+		/**
+		 * Determine if the specified port for this {@code SocketType} is
+		 * currently available on {@code localhost}.
+		 */
+		protected abstract boolean isPortAvailable(int port);
+
+		/**
+		 * Find a pseudo-random port number within the range
+		 * [{@code minPort}, {@code maxPort}].
+		 *
+		 * @param minPort
+		 * 		the minimum port number
+		 * @param maxPort
+		 * 		the maximum port number
+		 *
+		 * @return a random port number within the specified range
+		 */
+		private int findRandomPort(int minPort, int maxPort) {
+			int portRange = maxPort - minPort;
+			return minPort + random.nextInt(portRange);
+		}
+
+		/**
+		 * Find an available port for this {@code SocketType}, randomly selected
+		 * from the range [{@code minPort}, {@code maxPort}].
+		 *
+		 * @param minPort
+		 * 		the minimum port number
+		 * @param maxPort
+		 * 		the maximum port number
+		 *
+		 * @return an available port number for this socket type
+		 *
+		 * @throws IllegalStateException
+		 * 		if no available port could be found
+		 */
+		int findAvailablePort(int minPort, int maxPort) {
+			Assert.isTrue(minPort > 0, "'minPort' must be greater than 0");
+			Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'");
+			Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX);
+
+			int portRange = maxPort - minPort;
+			int candidatePort;
+			int searchCounter = 0;
+			do {
+				if(++searchCounter > portRange) {
+					throw new IllegalStateException(String.format(
+							"Could not find an available %s port in the range [%d, %d] after %d attempts", name(), minPort,
+							maxPort, searchCounter));
+				}
+				candidatePort = findRandomPort(minPort, maxPort);
+			} while(!isPortAvailable(candidatePort));
+
+			return candidatePort;
+		}
+
+		/**
+		 * Find the requested number of available ports for this {@code SocketType},
+		 * each randomly selected from the range [{@code minPort}, {@code maxPort}].
+		 *
+		 * @param numRequested
+		 * 		the number of available ports to find
+		 * @param minPort
+		 * 		the minimum port number
+		 * @param maxPort
+		 * 		the maximum port number
+		 *
+		 * @return a sorted set of available port numbers for this socket type
+		 *
+		 * @throws IllegalStateException
+		 * 		if the requested number of available ports could not be found
+		 */
+		SortedSet<Integer> findAvailablePorts(int numRequested, int minPort, int maxPort) {
+			Assert.isTrue(minPort > 0, "'minPort' must be greater than 0");
+			Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'");
+			Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX);
+			Assert.isTrue(numRequested > 0, "'numRequested' must be greater than 0");
+			Assert.isTrue((maxPort - minPort) >= numRequested,
+			              "'numRequested' must not be greater than 'maxPort' - 'minPort'");
+
+			final SortedSet<Integer> availablePorts = new TreeSet<Integer>();
+			int attemptCount = 0;
+			while((++attemptCount <= numRequested + 100) && (availablePorts.size() < numRequested)) {
+				availablePorts.add(findAvailablePort(minPort, maxPort));
+			}
+
+			if(availablePorts.size() != numRequested) {
+				throw new IllegalStateException(String.format(
+						"Could not find %d available %s ports in the range [%d, %d]", numRequested, name(), minPort,
+						maxPort));
+			}
+
+			return availablePorts;
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/udp/DatagramServer.java b/reactor-net/src/main/java/reactor/net/udp/DatagramServer.java
new file mode 100644
index 0000000..fc76e9c
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/udp/DatagramServer.java
@@ -0,0 +1,168 @@
+package reactor.net.udp;
+
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.core.composable.spec.Promises;
+import reactor.function.Consumer;
+import reactor.function.batch.BatchConsumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.AbstractNetPeer;
+import reactor.net.NetChannel;
+import reactor.net.NetServer;
+import reactor.net.config.ServerSocketOptions;
+import reactor.util.Assert;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.util.Collection;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class DatagramServer<IN, OUT>
+		extends AbstractNetPeer<IN, OUT>
+		implements NetServer<IN, OUT> {
+
+	private final InetSocketAddress   listenAddress;
+	private final NetworkInterface    multicastInterface;
+	private final ServerSocketOptions options;
+
+	protected DatagramServer(@Nonnull Environment env,
+	                         @Nonnull Reactor reactor,
+	                         @Nullable InetSocketAddress listenAddress,
+	                         @Nullable NetworkInterface multicastInterface,
+	                         @Nonnull ServerSocketOptions options,
+	                         @Nullable Codec<Buffer, IN, OUT> codec,
+	                         @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		super(env, reactor, codec, consumers);
+		Assert.notNull(options, "ServerSocketOptions cannot be null");
+		this.listenAddress = listenAddress;
+		this.multicastInterface = multicastInterface;
+		this.options = options;
+	}
+
+	/**
+	 * Start this server.
+	 *
+	 * @return {@literal this}
+	 */
+	public Promise<Boolean> start() {
+		final Deferred<Boolean, Promise<Boolean>> d = Promises.defer(getEnvironment(), getReactor().getDispatcher());
+		start(new Runnable() {
+			@Override
+			public void run() {
+				d.accept(true);
+			}
+		});
+		return d.compose();
+	}
+
+	@Override
+	public abstract DatagramServer<IN, OUT> start(@Nullable Runnable started);
+
+	/**
+	 * Send data to peers.
+	 *
+	 * @param data
+	 * 		the data to send
+	 *
+	 * @return {@literal this}
+	 */
+	public abstract DatagramServer<IN, OUT> send(OUT data);
+
+	/**
+	 * Retrieve the {@link reactor.core.composable.Stream} on which can be composed actions to take when data comes into
+	 * this {@literal DatagramServer}.
+	 *
+	 * @return the input {@link reactor.core.composable.Stream}
+	 */
+	public abstract Stream<IN> in();
+
+	/**
+	 * Retrieve the {@link reactor.core.composable.Stream} into which data can accepted for sending to peers.
+	 *
+	 * @return a {@link reactor.function.batch.BatchConsumer} for sending data out
+	 */
+	public abstract BatchConsumer<OUT> out();
+
+	/**
+	 * Join a multicast group.
+	 *
+	 * @param multicastAddress
+	 * 		multicast address of the group to join
+	 * @param iface
+	 * 		interface to use for multicast
+	 *
+	 * @return {@literal this}
+	 */
+	public abstract Promise<Void> join(InetAddress multicastAddress, NetworkInterface iface);
+
+	/**
+	 * Join a multicast group.
+	 *
+	 * @param multicastAddress
+	 * 		multicast address of the group to join
+	 *
+	 * @return {@literal this}
+	 */
+	public Promise<Void> join(InetAddress multicastAddress) {
+		return join(multicastAddress, null);
+	}
+
+	/**
+	 * Leave a multicast group.
+	 *
+	 * @param multicastAddress
+	 * 		multicast address of the group to leave
+	 * @param iface
+	 * 		interface to use for multicast
+	 *
+	 * @return {@literal this}
+	 */
+	public abstract Promise<Void> leave(InetAddress multicastAddress, NetworkInterface iface);
+
+	/**
+	 * Leave a multicast group.
+	 *
+	 * @param multicastAddress
+	 * 		multicast address of the group to leave
+	 *
+	 * @return {@literal this}
+	 */
+	public Promise<Void> leave(InetAddress multicastAddress) {
+		return leave(multicastAddress, null);
+	}
+
+	/**
+	 * Get the address to which this server is bound.
+	 *
+	 * @return
+	 */
+	protected InetSocketAddress getListenAddress() {
+		return listenAddress;
+	}
+
+	/**
+	 * Get the {@link java.net.NetworkInterface} on which multicast will be performed.
+	 *
+	 * @return
+	 */
+	protected NetworkInterface getMulticastInterface() { return multicastInterface; }
+
+	/**
+	 * Get the {@link reactor.net.config.ServerSocketOptions} currently in effect.
+	 *
+	 * @return
+	 */
+	protected ServerSocketOptions getOptions() {
+		return options;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/udp/spec/DatagramServerSpec.java b/reactor-net/src/main/java/reactor/net/udp/spec/DatagramServerSpec.java
new file mode 100644
index 0000000..ade2cef
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/udp/spec/DatagramServerSpec.java
@@ -0,0 +1,76 @@
+package reactor.net.udp.spec;
+
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.io.encoding.Codec;
+import reactor.net.config.ServerSocketOptions;
+import reactor.net.spec.NetServerSpec;
+import reactor.net.udp.DatagramServer;
+import reactor.util.Assert;
+
+import java.lang.reflect.Constructor;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.util.Collection;
+
+/**
+ * @author Jon Brisbin
+ */
+public class DatagramServerSpec<IN, OUT>
+		extends NetServerSpec<IN, OUT, DatagramServerSpec<IN, OUT>, DatagramServer<IN, OUT>> {
+
+	protected final Constructor<? extends DatagramServer> serverImplCtor;
+
+	private NetworkInterface multicastInterface;
+
+	public DatagramServerSpec(Class<? extends DatagramServer> serverImpl) {
+		Assert.notNull(serverImpl, "NetServer implementation class cannot be null.");
+		try {
+			this.serverImplCtor = serverImpl.getDeclaredConstructor(
+					Environment.class,
+					Reactor.class,
+					InetSocketAddress.class,
+					NetworkInterface.class,
+					ServerSocketOptions.class,
+					Codec.class,
+					Collection.class
+			);
+			this.serverImplCtor.setAccessible(true);
+		} catch(NoSuchMethodException e) {
+			throw new IllegalArgumentException(
+					"No public constructor found that matches the signature of the one found in the DatagramServer class.");
+		}
+	}
+
+	/**
+	 * Set the interface to use for multicast.
+	 *
+	 * @param iface
+	 * 		the {@link java.net.NetworkInterface} to use for multicast.
+	 *
+	 * @return {@literal this}
+	 */
+	public DatagramServerSpec<IN, OUT> multicastInterface(NetworkInterface iface) {
+		this.multicastInterface = iface;
+		return this;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected DatagramServer<IN, OUT> configure(Reactor reactor, Environment environment) {
+		try {
+			return serverImplCtor.newInstance(
+					environment,
+					reactor,
+					listenAddress,
+					multicastInterface,
+					options,
+					codec,
+					channelConsumers
+			);
+		} catch(Throwable t) {
+			throw new IllegalStateException(t);
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/udp/spec/DatagramServers.java b/reactor-net/src/main/java/reactor/net/udp/spec/DatagramServers.java
new file mode 100644
index 0000000..edda05a
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/udp/spec/DatagramServers.java
@@ -0,0 +1,56 @@
+package reactor.net.udp.spec;
+
+import reactor.core.Environment;
+import reactor.net.udp.DatagramServer;
+
+/**
+ * Helper class to make creating {@link reactor.net.udp.DatagramServer} instances in code more succinct.
+ *
+ * @author Jon Brisbin
+ */
+public class DatagramServers {
+
+	/**
+	 * Create a {@link reactor.net.udp.spec.DatagramServerSpec} for further configuration using the given {@link
+	 * reactor.core.Environment} and {@code serverImpl}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param serverImpl
+	 * 		the implementation of {@link reactor.net.udp.DatagramServer} to use
+	 * @param <IN>
+	 * 		type of the input
+	 * @param <OUT>
+	 * 		type of the output
+	 *
+	 * @return a new {@link reactor.net.udp.spec.DatagramServerSpec}
+	 */
+	public static <IN, OUT> DatagramServerSpec<IN, OUT> create(Environment env,
+	                                                           Class<? extends DatagramServer> serverImpl) {
+		return new DatagramServerSpec<IN, OUT>(serverImpl).env(env);
+	}
+
+	/**
+	 * Create a {@link reactor.net.udp.spec.DatagramServerSpec} for further configuration using the given {@link
+	 * reactor.core.Environment}, {@link reactor.event.dispatch.Dispatcher} type, and {@code serverImpl}.
+	 *
+	 * @param env
+	 * 		the {@link reactor.core.Environment} to use
+	 * @param dispatcher
+	 * 		the type of {@link reactor.event.dispatch.Dispatcher} to use
+	 * @param serverImpl
+	 * 		the implementation of {@link reactor.net.udp.DatagramServer} to use
+	 * @param <IN>
+	 * 		type of the input
+	 * @param <OUT>
+	 * 		type of the output
+	 *
+	 * @return a new {@link reactor.net.udp.spec.DatagramServerSpec}
+	 */
+	public static <IN, OUT> DatagramServerSpec<IN, OUT> create(Environment env,
+	                                                           String dispatcher,
+	                                                           Class<? extends DatagramServer> serverImpl) {
+		return new DatagramServerSpec<IN, OUT>(serverImpl).env(env).dispatcher(dispatcher);
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/zmq/ZeroMQClientSocketOptions.java b/reactor-net/src/main/java/reactor/net/zmq/ZeroMQClientSocketOptions.java
new file mode 100644
index 0000000..a4f3d26
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/zmq/ZeroMQClientSocketOptions.java
@@ -0,0 +1,97 @@
+package reactor.net.zmq;
+
+import org.zeromq.ZContext;
+import org.zeromq.ZMQ;
+import reactor.function.Consumer;
+import reactor.net.config.ClientSocketOptions;
+import reactor.util.Assert;
+
+/**
+ * {@link reactor.net.config.ClientSocketOptions} that include ZeroMQ-specific configuration options.
+ *
+ * @author Jon Brisbin
+ */
+public class ZeroMQClientSocketOptions extends ClientSocketOptions {
+
+	private ZContext context;
+	private int socketType = ZMQ.ROUTER;
+	private Consumer<ZMQ.Socket> socketConfigurer;
+	private String connectAddresses;
+
+	/**
+	 * Get the {@link org.zeromq.ZContext} to use for IO.
+	 *
+	 * @return the {@link org.zeromq.ZContext} to use
+	 */
+	public ZContext context() {
+		return context;
+	}
+
+	/**
+	 * Set the {@link org.zeromq.ZContext} to use for IO.
+	 *
+	 * @param context
+	 * 		the {@link org.zeromq.ZContext} to use
+	 *
+	 * @return {@literal this}
+	 */
+	public ZeroMQClientSocketOptions context(ZContext context) {
+		Assert.notNull(context, "ZeroMQ Context cannot be null");
+		this.context = context;
+		return this;
+	}
+
+	/**
+	 * The type of the ZMQ socket to create.
+	 *
+	 * @return the ZMQ socket type
+	 */
+	public int socketType() {
+		return socketType;
+	}
+
+	/**
+	 * Set the type of ZMQ socket to create.
+	 *
+	 * @param socketType
+	 * 		the ZMQ socket type
+	 *
+	 * @return {@literal this}
+	 */
+	public ZeroMQClientSocketOptions socketType(int socketType) {
+		this.socketType = socketType;
+		return this;
+	}
+
+	/**
+	 * The {@link reactor.function.Consumer} responsible for configuring the underlying ZeroMQ socket.
+	 *
+	 * @return the ZMQ.Socket configurer
+	 */
+	public Consumer<ZMQ.Socket> socketConfigurer() {
+		return socketConfigurer;
+	}
+
+	/**
+	 * Set the {@link reactor.function.Consumer} responsible for configure the underlying ZeroMQ socket.
+	 *
+	 * @param socketConfigurer
+	 * 		the ZMQ.Socket configurer
+	 *
+	 * @return {@literal this}
+	 */
+	public ZeroMQClientSocketOptions socketConfigurer(Consumer<ZMQ.Socket> socketConfigurer) {
+		this.socketConfigurer = socketConfigurer;
+		return this;
+	}
+
+	public String connectAddresses() {
+		return connectAddresses;
+	}
+
+	public ZeroMQClientSocketOptions connectAddresses(String connectAddresses) {
+		this.connectAddresses = connectAddresses;
+		return this;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/zmq/ZeroMQNetChannel.java b/reactor-net/src/main/java/reactor/net/zmq/ZeroMQNetChannel.java
new file mode 100644
index 0000000..360c9f9
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/zmq/ZeroMQNetChannel.java
@@ -0,0 +1,165 @@
+package reactor.net.zmq;
+
+import com.gs.collections.api.list.MutableList;
+import com.gs.collections.impl.block.predicate.checked.CheckedPredicate;
+import com.gs.collections.impl.list.mutable.FastList;
+import com.gs.collections.impl.list.mutable.SynchronizedMutableList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.zeromq.ZFrame;
+import org.zeromq.ZMQ;
+import org.zeromq.ZMsg;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.event.dispatch.Dispatcher;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.AbstractNetChannel;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ZeroMQNetChannel<IN, OUT> extends AbstractNetChannel<IN, OUT> {
+
+	private static final AtomicReferenceFieldUpdater<ZeroMQNetChannel, ZMsg> MSG_UPD =
+			AtomicReferenceFieldUpdater.newUpdater(ZeroMQNetChannel.class, ZMsg.class, "currentMsg");
+
+	private final Logger                log           = LoggerFactory.getLogger(getClass());
+	private final ZeroMQConsumerSpec    eventSpec     = new ZeroMQConsumerSpec();
+	private final MutableList<Runnable> closeHandlers = SynchronizedMutableList.of(FastList.<Runnable>newList());
+
+	private volatile String     connectionId;
+	private volatile ZMQ.Socket socket;
+	private volatile ZMsg       currentMsg;
+
+	public ZeroMQNetChannel(@Nonnull Environment env,
+	                        @Nonnull Reactor eventsReactor,
+	                        @Nonnull Dispatcher ioDispatcher,
+	                        @Nullable Codec<Buffer, IN, OUT> codec) {
+		super(env, codec, ioDispatcher, eventsReactor);
+	}
+
+	public ZeroMQNetChannel<IN, OUT> setConnectionId(String connectionId) {
+		this.connectionId = connectionId;
+		return this;
+	}
+
+	public ZeroMQNetChannel<IN, OUT> setSocket(ZMQ.Socket socket) {
+		this.socket = socket;
+		return this;
+	}
+
+	@Override
+	public InetSocketAddress remoteAddress() {
+		return null;
+	}
+
+	@Override
+	protected void write(ByteBuffer data, final Deferred<Void, Promise<Void>> onComplete, boolean flush) {
+		byte[] bytes = new byte[data.remaining()];
+		data.get(bytes);
+		boolean isNewMsg = MSG_UPD.compareAndSet(this, null, new ZMsg());
+		ZMsg msg = MSG_UPD.get(this);
+		if (isNewMsg) {
+			switch (socket.getType()) {
+				case ZMQ.ROUTER:
+					msg.add(new ZFrame(connectionId));
+					break;
+				default:
+			}
+		}
+		msg.add(new ZFrame(bytes));
+
+		if (flush) {
+			doFlush(onComplete);
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	protected void write(Object data, Deferred<Void, Promise<Void>> onComplete, boolean flush) {
+		Buffer buff = getEncoder().apply((OUT) data);
+		write(buff.byteBuffer(), onComplete, flush);
+	}
+
+	@Override
+	protected synchronized void flush() {
+		doFlush(null);
+	}
+
+	private void doFlush(final Deferred<Void, Promise<Void>> onComplete) {
+		ZMsg msg = MSG_UPD.get(ZeroMQNetChannel.this);
+		MSG_UPD.compareAndSet(ZeroMQNetChannel.this, msg, null);
+		if (null != msg) {
+			boolean success = msg.send(socket);
+			if (null != onComplete) {
+				if (success) {
+					onComplete.accept((Void) null);
+				} else {
+					onComplete.accept(new RuntimeException("ZeroMQ Message could not be sent"));
+				}
+			}
+		}
+	}
+
+	@Override
+	public void close(final Consumer<Boolean> onClose) {
+		getEventsReactor().schedule(new Consumer<Void>() {
+			@Override
+			public void accept(Void v) {
+				closeHandlers.removeIf(new CheckedPredicate<Runnable>() {
+					@Override
+					public boolean safeAccept(Runnable r) throws Exception {
+						r.run();
+						return true;
+					}
+				});
+				if (null != onClose) {
+					onClose.accept(true);
+				}
+			}
+		}, null);
+	}
+
+	@Override
+	public ConsumerSpec on() {
+		return eventSpec;
+	}
+
+	@Override
+	public String toString() {
+		return "ZeroMQNetChannel{" +
+				"closeHandlers=" + closeHandlers +
+				", connectionId='" + connectionId + '\'' +
+				", socket=" + socket +
+				'}';
+	}
+
+	private class ZeroMQConsumerSpec implements ConsumerSpec {
+		@Override
+		public ConsumerSpec close(Runnable onClose) {
+			closeHandlers.add(onClose);
+			return this;
+		}
+
+		@Override
+		public ConsumerSpec readIdle(long idleTimeout, Runnable onReadIdle) {
+			return this;
+		}
+
+		@Override
+		public ConsumerSpec writeIdle(long idleTimeout, Runnable onWriteIdle) {
+			return this;
+		}
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/zmq/ZeroMQServerSocketOptions.java b/reactor-net/src/main/java/reactor/net/zmq/ZeroMQServerSocketOptions.java
new file mode 100644
index 0000000..7fafac3
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/zmq/ZeroMQServerSocketOptions.java
@@ -0,0 +1,99 @@
+package reactor.net.zmq;
+
+import org.zeromq.ZContext;
+import org.zeromq.ZMQ;
+import reactor.function.Consumer;
+import reactor.net.config.ServerSocketOptions;
+import reactor.util.Assert;
+
+/**
+ * {@link reactor.net.config.ServerSocketOptions} that include ZeroMQ-specific configuration options.
+ *
+ * @author Jon Brisbin
+ */
+public class ZeroMQServerSocketOptions extends ServerSocketOptions {
+
+	private ZContext context;
+	private int socketType = ZMQ.ROUTER;
+	private Consumer<ZMQ.Socket> socketConfigurer;
+	private String               listenAddresses;
+
+	/**
+	 * Get the {@link org.zeromq.ZMQ.Context} to use for IO.
+	 *
+	 * @return the {@link org.zeromq.ZMQ.Context} to use
+	 */
+	public ZContext context() {
+		return context;
+	}
+
+	/**
+	 * Set the {@link org.zeromq.ZMQ.Context} to use for IO.
+	 *
+	 * @param context
+	 * 		the {@link org.zeromq.ZMQ.Context} to use
+	 *
+	 * @return {@literal this}
+	 */
+	public ZeroMQServerSocketOptions context(ZContext context) {
+		Assert.notNull(context, "ZeroMQ Context cannot be null");
+		this.context = context;
+		return this;
+	}
+
+	/**
+	 * The type of the ZMQ socket to create.
+	 *
+	 * @return the ZMQ socket type
+	 */
+	public int socketType() {
+		return socketType;
+	}
+
+	/**
+	 * Set the type of ZMQ socket to create.
+	 *
+	 * @param socketType
+	 * 		the ZMQ socket type
+	 *
+	 * @return {@literal this}
+	 */
+	public ZeroMQServerSocketOptions socketType(int socketType) {
+		this.socketType = socketType;
+		return this;
+	}
+
+
+	/**
+	 * The {@link reactor.function.Consumer} responsible for configuring the underlying ZeroMQ socket.
+	 *
+	 * @return the ZMQ.Socket configurer
+	 */
+	public Consumer<ZMQ.Socket> socketConfigurer() {
+		return socketConfigurer;
+	}
+
+	/**
+	 * Set the {@link reactor.function.Consumer} responsible for configure the underlying ZeroMQ socket.
+	 *
+	 * @param socketConfigurer
+	 * 		the ZMQ.Socket configurer
+	 *
+	 * @return {@literal this}
+	 */
+	public ZeroMQServerSocketOptions socketConfigurer(Consumer<ZMQ.Socket> socketConfigurer) {
+		this.socketConfigurer = socketConfigurer;
+		return this;
+	}
+
+
+	public String listenAddresses() {
+		return listenAddresses;
+	}
+
+	public ZeroMQServerSocketOptions listenAddresses(String listenAddresses) {
+		this.listenAddresses = listenAddresses;
+		return this;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/zmq/ZeroMQWorker.java b/reactor-net/src/main/java/reactor/net/zmq/ZeroMQWorker.java
new file mode 100644
index 0000000..eddd299
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/zmq/ZeroMQWorker.java
@@ -0,0 +1,114 @@
+package reactor.net.zmq;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.zeromq.*;
+import reactor.io.Buffer;
+
+import java.util.UUID;
+
+/**
+ * @author Jon Brisbin
+ */
+public abstract class ZeroMQWorker<IN, OUT> implements Runnable {
+
+	private final Logger log   = LoggerFactory.getLogger(getClass());
+	private final ZLoop  zloop = new ZLoop();
+
+	private final UUID                id;
+	private final int                 socketType;
+	private final int                 ioThreadCount;
+	private final ZLoop.IZLoopHandler inputHandler;
+
+	private volatile boolean      closed;
+	private volatile boolean      shutdownCtx;
+	private volatile ZContext     zmq;
+	private volatile ZMQ.Socket   socket;
+	private volatile ZMQ.PollItem pollin;
+
+	public ZeroMQWorker(UUID id, int socketType, int ioThreadCount, ZContext zmq) {
+		this.id = id;
+		this.socketType = socketType;
+		this.ioThreadCount = ioThreadCount;
+		this.zmq = zmq;
+		this.inputHandler = new ZLoop.IZLoopHandler() {
+			@Override
+			public int handle(ZLoop loop, ZMQ.PollItem item, Object arg) {
+				ZMsg msg = ZMsg.recvMsg(socket);
+				if (null == msg || msg.size() == 0) {
+					return 0;
+				}
+				if (closed) {
+					return -1;
+				}
+
+				String connId;
+				switch (ZeroMQWorker.this.socketType) {
+					case ZMQ.ROUTER:
+						connId = msg.popString();
+						break;
+					default:
+						connId = ZeroMQWorker.this.id.toString();
+				}
+				ZeroMQNetChannel<IN, OUT> netChannel = select(connId)
+						.setConnectionId(connId)
+						.setSocket(socket);
+
+				ZFrame content;
+				while (null != (content = msg.pop())) {
+					netChannel.read(Buffer.wrap(content.getData()));
+				}
+				msg.destroy();
+
+				return 0;
+			}
+		};
+	}
+
+	@Override
+	public void run() {
+		if (closed) {
+			return;
+		}
+		if (null == zmq) {
+			zmq = new ZContext(ioThreadCount);
+			shutdownCtx = true;
+		}
+		socket = zmq.createSocket(socketType);
+		socket.setIdentity(id.toString().getBytes());
+		configure(socket);
+
+		pollin = new ZMQ.PollItem(socket, ZMQ.Poller.POLLIN);
+		if (log.isTraceEnabled()) {
+			zloop.verbose(true);
+		}
+		zloop.addPoller(pollin, inputHandler, null);
+
+		start(socket);
+
+		zloop.start();
+
+		zmq.destroySocket(socket);
+	}
+
+	public void shutdown() {
+		if (closed) {
+			return;
+		}
+		zloop.removePoller(pollin);
+		zloop.destroy();
+
+		closed = true;
+
+		if (shutdownCtx) {
+			zmq.destroy();
+		}
+	}
+
+	protected abstract void configure(ZMQ.Socket socket);
+
+	protected abstract void start(ZMQ.Socket socket);
+
+	protected abstract ZeroMQNetChannel<IN, OUT> select(Object id);
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQ.java b/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQ.java
new file mode 100644
index 0000000..8ec9005
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQ.java
@@ -0,0 +1,191 @@
+package reactor.net.zmq.tcp;
+
+import com.gs.collections.api.list.MutableList;
+import com.gs.collections.impl.block.function.checked.CheckedFunction0;
+import com.gs.collections.impl.block.predicate.checked.CheckedPredicate;
+import com.gs.collections.impl.list.mutable.FastList;
+import com.gs.collections.impl.list.mutable.SynchronizedMutableList;
+import com.gs.collections.impl.map.mutable.SynchronizedMutableMap;
+import com.gs.collections.impl.map.mutable.UnifiedMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.zeromq.ZContext;
+import org.zeromq.ZMQ;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.spec.Promises;
+import reactor.core.spec.Reactors;
+import reactor.event.dispatch.Dispatcher;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.io.encoding.StandardCodecs;
+import reactor.net.NetChannel;
+import reactor.net.tcp.TcpClient;
+import reactor.net.tcp.TcpServer;
+import reactor.net.tcp.spec.TcpClientSpec;
+import reactor.net.tcp.spec.TcpServerSpec;
+import reactor.net.zmq.ZeroMQClientSocketOptions;
+import reactor.net.zmq.ZeroMQServerSocketOptions;
+import reactor.support.NamedDaemonThreadFactory;
+import reactor.util.Assert;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ZeroMQ<T> {
+
+	private static final SynchronizedMutableMap<Integer, String> SOCKET_TYPES = SynchronizedMutableMap.of(UnifiedMap.<Integer, String>newMap());
+
+	private final Logger                       log     = LoggerFactory.getLogger(getClass());
+	private final MutableList<TcpClient<T, T>> clients = SynchronizedMutableList.of(FastList.<TcpClient<T, T>>newList());
+	private final MutableList<TcpServer<T, T>> servers = SynchronizedMutableList.of(FastList.<TcpServer<T, T>>newList());
+
+	private final ExecutorService threadPool = Executors.newCachedThreadPool(new NamedDaemonThreadFactory("zmq"));
+
+	private final Environment env;
+	private final Dispatcher  dispatcher;
+	private final Reactor     reactor;
+	private final ZContext    zmqCtx;
+
+	@SuppressWarnings("unchecked")
+	private volatile Codec<Buffer, T, T> codec    = (Codec<Buffer, T, T>) StandardCodecs.PASS_THROUGH_CODEC;
+	private volatile boolean             shutdown = false;
+
+	public ZeroMQ(Environment env) {
+		this(env, env.getDefaultDispatcher());
+	}
+
+	public ZeroMQ(Environment env, String dispatcher) {
+		this(env, env.getDispatcher(dispatcher));
+	}
+
+	public ZeroMQ(Environment env, Dispatcher dispatcher) {
+		this.env = env;
+		this.dispatcher = dispatcher;
+		this.reactor = Reactors.reactor(env, dispatcher);
+		this.zmqCtx = new ZContext();
+		this.zmqCtx.setLinger(100);
+	}
+
+	public static String findSocketTypeName(final int socketType) {
+		return SOCKET_TYPES.getIfAbsentPut(socketType, new CheckedFunction0<String>() {
+			@Override
+			public String safeValue() throws Exception {
+				for (Field f : ZMQ.class.getDeclaredFields()) {
+					if (int.class.isAssignableFrom(f.getType())) {
+						f.setAccessible(true);
+						try {
+							int val = f.getInt(null);
+							if (socketType == val) {
+								return f.getName();
+							}
+						} catch (IllegalAccessException e) {
+						}
+					}
+				}
+				return "";
+			}
+		});
+	}
+
+	public ZeroMQ<T> codec(Codec<Buffer, T, T> codec) {
+		this.codec = codec;
+		return this;
+	}
+
+	public Promise<NetChannel<T, T>> dealer(String addrs) {
+		return createClient(addrs, ZMQ.DEALER);
+	}
+
+	public Promise<NetChannel<T, T>> push(String addrs) {
+		return createClient(addrs, ZMQ.PUSH);
+	}
+
+	public Promise<NetChannel<T, T>> pull(String addrs) {
+		return createServer(addrs, ZMQ.PULL);
+	}
+
+	public Promise<NetChannel<T, T>> request(String addrs) {
+		return createClient(addrs, ZMQ.REQ);
+	}
+
+	public Promise<NetChannel<T, T>> reply(String addrs) {
+		return createServer(addrs, ZMQ.REP);
+	}
+
+	public Promise<NetChannel<T, T>> router(String addrs) {
+		return createServer(addrs, ZMQ.ROUTER);
+	}
+
+	private Promise<NetChannel<T, T>> createClient(String addrs, int socketType) {
+		Assert.isTrue(!shutdown, "This ZeroMQ instance has been shut down");
+
+		TcpClient<T, T> client = new TcpClientSpec<T, T>(ZeroMQTcpClient.class)
+				.env(env).dispatcher(dispatcher).codec(codec)
+				.options(new ZeroMQClientSocketOptions()
+						         .context(zmqCtx)
+						         .connectAddresses(addrs)
+						         .socketType(socketType))
+				.get();
+
+		clients.add(client);
+
+		return client.open();
+	}
+
+	public Promise<NetChannel<T, T>> createServer(String addrs, int socketType) {
+		Assert.isTrue(!shutdown, "This ZeroMQ instance has been shut down");
+
+		Deferred<NetChannel<T, T>, Promise<NetChannel<T, T>>> d = Promises.defer(env, dispatcher);
+
+		TcpServer<T, T> server = new TcpServerSpec<T, T>(ZeroMQTcpServer.class)
+				.env(env).dispatcher(dispatcher).codec(codec)
+				.options(new ZeroMQServerSocketOptions()
+						         .context(zmqCtx)
+						         .listenAddresses(addrs)
+						         .socketType(socketType))
+				.consume(d)
+				.get();
+
+		servers.add(server);
+
+		server.start();
+
+		return d.compose();
+	}
+
+	public void shutdown() {
+		if (shutdown) {
+			return;
+		}
+		shutdown = true;
+
+		servers.removeIf(new CheckedPredicate<TcpServer<T, T>>() {
+			@Override
+			public boolean safeAccept(TcpServer<T, T> server) throws Exception {
+				Assert.isTrue(server.shutdown().await(60, TimeUnit.SECONDS), "Server " + server + " not properly shut down");
+				return true;
+			}
+		});
+		clients.removeIf(new CheckedPredicate<TcpClient<T, T>>() {
+			@Override
+			public boolean safeAccept(TcpClient<T, T> client) throws Exception {
+				Assert.isTrue(client.close().await(60, TimeUnit.SECONDS), "Client " + client + " not properly shut down");
+				return true;
+			}
+		});
+
+//		if (log.isDebugEnabled()) {
+//			log.debug("Destroying {} on {}", zmqCtx, this);
+//		}
+//		zmqCtx.destroy();
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQTcpClient.java b/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQTcpClient.java
new file mode 100644
index 0000000..03febc9
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQTcpClient.java
@@ -0,0 +1,187 @@
+package reactor.net.zmq.tcp;
+
+import com.gs.collections.api.map.MutableMap;
+import com.gs.collections.impl.block.procedure.checked.CheckedProcedure2;
+import com.gs.collections.impl.map.mutable.SynchronizedMutableMap;
+import com.gs.collections.impl.map.mutable.UnifiedMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.zeromq.ZContext;
+import org.zeromq.ZMQ;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.Stream;
+import reactor.core.composable.spec.Promises;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.NetChannel;
+import reactor.net.Reconnect;
+import reactor.net.config.ClientSocketOptions;
+import reactor.net.config.SslOptions;
+import reactor.net.tcp.TcpClient;
+import reactor.net.zmq.ZeroMQClientSocketOptions;
+import reactor.net.zmq.ZeroMQNetChannel;
+import reactor.net.zmq.ZeroMQWorker;
+import reactor.support.NamedDaemonThreadFactory;
+import reactor.util.UUIDUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import static reactor.net.zmq.tcp.ZeroMQ.findSocketTypeName;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ZeroMQTcpClient<IN, OUT> extends TcpClient<IN, OUT> {
+
+	private final Logger                                       log     = LoggerFactory.getLogger(getClass());
+	private final MutableMap<ZeroMQWorker<IN, OUT>, Future<?>> workers =
+			SynchronizedMutableMap.of(UnifiedMap.<ZeroMQWorker<IN, OUT>, Future<?>>newMap());
+
+	private final int                       ioThreadCount;
+	private final ZeroMQClientSocketOptions zmqOpts;
+	private final ExecutorService           threadPool;
+
+	public ZeroMQTcpClient(@Nonnull Environment env,
+	                       @Nonnull Reactor reactor,
+	                       @Nonnull InetSocketAddress connectAddress,
+	                       @Nullable ClientSocketOptions options,
+	                       @Nullable SslOptions sslOptions,
+	                       @Nullable Codec<Buffer, IN, OUT> codec,
+	                       @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		super(env, reactor, connectAddress, options, sslOptions, codec, consumers);
+
+		this.ioThreadCount = env.getProperty("reactor.zmq.ioThreadCount", Integer.class, 1);
+
+		if (options instanceof ZeroMQClientSocketOptions) {
+			this.zmqOpts = (ZeroMQClientSocketOptions) options;
+		} else {
+			this.zmqOpts = null;
+		}
+
+		this.threadPool = Executors.newCachedThreadPool(new NamedDaemonThreadFactory("zmq-client"));
+	}
+
+	@Override
+	public Promise<NetChannel<IN, OUT>> open() {
+		Deferred<NetChannel<IN, OUT>, Promise<NetChannel<IN, OUT>>> d =
+				Promises.defer(getEnvironment(), getReactor().getDispatcher());
+
+		doOpen(d);
+
+		return d.compose();
+	}
+
+	@Override
+	public Stream<NetChannel<IN, OUT>> open(Reconnect reconnect) {
+		throw new IllegalStateException("Reconnects are handled transparently by the ZeroMQ network library");
+	}
+
+	@Override
+	public void close(@Nullable Consumer<Boolean> onClose) {
+		if (workers.isEmpty()) {
+			throw new IllegalStateException("This ZeroMQ server has not been started");
+		}
+
+		super.close(null);
+
+		workers.forEachKeyValue(new CheckedProcedure2<ZeroMQWorker<IN, OUT>, Future<?>>() {
+			@Override
+			public void safeValue(ZeroMQWorker<IN, OUT> w, Future<?> f) throws Exception {
+				w.shutdown();
+				if (!f.isDone()) {
+					f.cancel(true);
+				}
+			}
+		});
+		threadPool.shutdownNow();
+
+		getReactor().schedule(onClose, true);
+		notifyShutdown();
+	}
+
+	@Override
+	protected <C> NetChannel<IN, OUT> createChannel(C ioChannel) {
+		final ZeroMQNetChannel<IN, OUT> ch = new ZeroMQNetChannel<IN, OUT>(
+				getEnvironment(),
+				getReactor(),
+				getReactor().getDispatcher(),
+				getCodec()
+		);
+		ch.on().close(new Runnable() {
+			@Override
+			public void run() {
+				notifyClose(ch);
+			}
+		});
+		return ch;
+	}
+
+	private void doOpen(final Consumer<NetChannel<IN, OUT>> consumer) {
+		final UUID id = UUIDUtils.random();
+
+		int socketType = (null != zmqOpts ? zmqOpts.socketType() : ZMQ.DEALER);
+		ZContext zmq = (null != zmqOpts ? zmqOpts.context() : null);
+
+		ZeroMQWorker<IN, OUT> worker = new ZeroMQWorker<IN, OUT>(id, socketType, ioThreadCount, zmq) {
+			@Override
+			protected void configure(ZMQ.Socket socket) {
+				socket.setReceiveBufferSize(getOptions().rcvbuf());
+				socket.setSendBufferSize(getOptions().sndbuf());
+				if (getOptions().keepAlive()) {
+					socket.setTCPKeepAlive(1);
+				}
+				if (null != zmqOpts && null != zmqOpts.socketConfigurer()) {
+					zmqOpts.socketConfigurer().accept(socket);
+				}
+			}
+
+			@Override
+			protected void start(final ZMQ.Socket socket) {
+				String addr = createConnectAddress();
+				if (log.isInfoEnabled()) {
+					String type = findSocketTypeName(socket.getType());
+					log.info("CONNECT: connecting ZeroMQ {} socket to {}", type, addr);
+				}
+
+				socket.connect(addr);
+				notifyStart(new Runnable() {
+					@Override
+					public void run() {
+						ZeroMQNetChannel<IN, OUT> ch = select(id.toString())
+								.setConnectionId(id.toString())
+								.setSocket(socket);
+						consumer.accept(ch);
+					}
+				});
+			}
+
+			@Override
+			protected ZeroMQNetChannel<IN, OUT> select(Object id) {
+				return (ZeroMQNetChannel<IN, OUT>) ZeroMQTcpClient.this.select(id);
+			}
+		};
+		workers.put(worker, threadPool.submit(worker));
+	}
+
+	private String createConnectAddress() {
+		String addrs;
+		if (null != zmqOpts && null != zmqOpts.connectAddresses()) {
+			addrs = zmqOpts.connectAddresses();
+		} else {
+			addrs = "tcp://" + getConnectAddress().getHostString() + ":" + getConnectAddress().getPort();
+		}
+		return addrs;
+	}
+
+}
diff --git a/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQTcpServer.java b/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQTcpServer.java
new file mode 100644
index 0000000..8e0617a
--- /dev/null
+++ b/reactor-net/src/main/java/reactor/net/zmq/tcp/ZeroMQTcpServer.java
@@ -0,0 +1,152 @@
+package reactor.net.zmq.tcp;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.zeromq.ZContext;
+import org.zeromq.ZMQ;
+import reactor.core.Environment;
+import reactor.core.Reactor;
+import reactor.core.composable.Deferred;
+import reactor.core.composable.Promise;
+import reactor.core.composable.spec.Promises;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.net.NetChannel;
+import reactor.net.config.ServerSocketOptions;
+import reactor.net.config.SslOptions;
+import reactor.net.tcp.TcpServer;
+import reactor.net.zmq.ZeroMQNetChannel;
+import reactor.net.zmq.ZeroMQServerSocketOptions;
+import reactor.net.zmq.ZeroMQWorker;
+import reactor.support.NamedDaemonThreadFactory;
+import reactor.util.Assert;
+import reactor.util.UUIDUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import static reactor.net.zmq.tcp.ZeroMQ.findSocketTypeName;
+
+/**
+ * @author Jon Brisbin
+ */
+public class ZeroMQTcpServer<IN, OUT> extends TcpServer<IN, OUT> {
+
+	private final Logger log = LoggerFactory.getLogger(getClass());
+
+	private final int                       ioThreadCount;
+	private final ZeroMQServerSocketOptions zmqOpts;
+	private final ExecutorService           threadPool;
+
+	private volatile ZeroMQWorker<IN, OUT> worker;
+	private volatile Future<?>             workerFuture;
+
+	public ZeroMQTcpServer(@Nonnull Environment env,
+	                       @Nonnull Reactor reactor,
+	                       @Nullable InetSocketAddress listenAddress,
+	                       ServerSocketOptions options,
+	                       SslOptions sslOptions,
+	                       @Nullable Codec<Buffer, IN, OUT> codec,
+	                       @Nonnull Collection<Consumer<NetChannel<IN, OUT>>> consumers) {
+		super(env, reactor, listenAddress, options, sslOptions, codec, consumers);
+
+		this.ioThreadCount = env.getProperty("reactor.zmq.ioThreadCount", Integer.class, 1);
+
+		if (options instanceof ZeroMQServerSocketOptions) {
+			this.zmqOpts = (ZeroMQServerSocketOptions) options;
+		} else {
+			this.zmqOpts = null;
+		}
+
+		this.threadPool = Executors.newCachedThreadPool(new NamedDaemonThreadFactory("zmq-server"));
+	}
+
+	@Override
+	public TcpServer<IN, OUT> start(@Nullable final Runnable started) {
+		Assert.isNull(worker, "This ZeroMQ server has already been started");
+
+		UUID id = UUIDUtils.random();
+		int socketType = (null != zmqOpts ? zmqOpts.socketType() : ZMQ.ROUTER);
+		ZContext zmq = (null != zmqOpts ? zmqOpts.context() : null);
+		this.worker = new ZeroMQWorker<IN, OUT>(id, socketType, ioThreadCount, zmq) {
+			@Override
+			protected void configure(ZMQ.Socket socket) {
+				socket.setReceiveBufferSize(getOptions().rcvbuf());
+				socket.setSendBufferSize(getOptions().sndbuf());
+				socket.setBacklog(getOptions().backlog());
+				if (getOptions().keepAlive()) {
+					socket.setTCPKeepAlive(1);
+				}
+				if (null != zmqOpts && null != zmqOpts.socketConfigurer()) {
+					zmqOpts.socketConfigurer().accept(socket);
+				}
+			}
+
+			@Override
+			protected void start(ZMQ.Socket socket) {
+				String addr;
+				if (null != zmqOpts && null != zmqOpts.listenAddresses()) {
+					addr = zmqOpts.listenAddresses();
+				} else {
+					addr = "tcp://" + getListenAddress().getHostString() + ":" + getListenAddress().getPort();
+				}
+				if (log.isInfoEnabled()) {
+					String type = findSocketTypeName(socket.getType());
+					log.info("BIND: starting ZeroMQ {} socket on {}", type, addr);
+				}
+
+				socket.bind(addr);
+				notifyStart(started);
+			}
+
+			@Override
+			protected ZeroMQNetChannel<IN, OUT> select(Object id) {
+				return (ZeroMQNetChannel<IN, OUT>) ZeroMQTcpServer.this.select(id);
+			}
+		};
+		this.workerFuture = threadPool.submit(this.worker);
+
+		return this;
+	}
+
+	@Override
+	protected <C> NetChannel<IN, OUT> createChannel(C ioChannel) {
+		return new ZeroMQNetChannel<IN, OUT>(
+				getEnvironment(),
+				getReactor(),
+				getReactor().getDispatcher(),
+				getCodec()
+		);
+	}
+
+	@Override
+	public Promise<Boolean> shutdown() {
+		if (null == worker) {
+			return Promises.<Boolean>error(new IllegalStateException("This ZeroMQ server has not been started")).get();
+		}
+
+		Deferred<Boolean, Promise<Boolean>> d = Promises.defer(getEnvironment(), getReactor().getDispatcher());
+
+		super.close(null);
+
+		worker.shutdown();
+		if (!workerFuture.isDone()) {
+			workerFuture.cancel(true);
+		}
+		threadPool.shutdownNow();
+
+		notifyShutdown();
+		d.accept(true);
+
+
+		return d.compose();
+	}
+
+}
diff --git a/reactor-net/src/test/groovy/reactor/net/tcp/encoding/SyslogCodecSpec.groovy b/reactor-net/src/test/groovy/reactor/net/tcp/encoding/SyslogCodecSpec.groovy
new file mode 100644
index 0000000..b8d8dec
--- /dev/null
+++ b/reactor-net/src/test/groovy/reactor/net/tcp/encoding/SyslogCodecSpec.groovy
@@ -0,0 +1,26 @@
+package reactor.net.tcp.encoding
+
+import reactor.io.Buffer
+import reactor.net.encoding.syslog.SyslogCodec
+import spock.lang.Specification
+
+/**
+ * @author Jon Brisbin
+ */
+class SyslogCodecSpec extends Specification{
+
+	def "SyslogCodec can decode syslog messages"() {
+		given: "syslog data"
+			def codec = new SyslogCodec()
+			def data = Buffer.wrap("<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on /dev/pts/8\n")
+			def host = ""
+
+		when: "data is decoded"
+			def msg = codec.decoder(null).apply(data)
+			host = msg.host
+
+		then: "data was decoded"
+			host == "mymachine"
+	}
+
+}
diff --git a/reactor-net/src/test/groovy/reactor/net/tcp/netty/ClientServerIntegrationSpec.groovy b/reactor-net/src/test/groovy/reactor/net/tcp/netty/ClientServerIntegrationSpec.groovy
new file mode 100644
index 0000000..bcf7ce4
--- /dev/null
+++ b/reactor-net/src/test/groovy/reactor/net/tcp/netty/ClientServerIntegrationSpec.groovy
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.netty
+
+import reactor.core.Environment
+import reactor.function.Consumer
+import reactor.io.encoding.LengthFieldCodec
+import reactor.io.encoding.json.JsonCodec
+import reactor.net.NetChannel
+import reactor.net.netty.tcp.NettyTcpClient
+import reactor.net.netty.tcp.NettyTcpServer
+import reactor.net.tcp.spec.TcpClientSpec
+import reactor.net.tcp.spec.TcpServerSpec
+import reactor.net.tcp.support.SocketUtils
+import spock.lang.Ignore
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+import static org.junit.Assert.assertNotNull
+
+ at Ignore
+class ClientServerIntegrationSpec extends Specification {
+
+	Environment env1
+	Environment env2
+
+	def setup() {
+		env1 = new Environment()
+		env2 = new Environment()
+	}
+
+	@Unroll
+	def "Client should be able to send data to server"(List<Pojo> data) {
+		given: "a TcpServer and TcpClient with JSON codec"
+			def dataLatch = new CountDownLatch(data.size())
+
+			final int port = SocketUtils.findAvailableTcpPort()
+
+			def consumerMock = Mock(Consumer) { data.size() * accept(_) }
+			def codec = new LengthFieldCodec(new JsonCodec(Pojo))
+
+			def server = new TcpServerSpec<Pojo, Pojo>(NettyTcpServer).
+					env(env1).dispatcher("sync").
+					listen(port).
+					codec(codec).
+					consume({ conn ->
+						conn.consume({ pojo ->
+							dataLatch.countDown()
+							consumerMock.accept(pojo)
+						} as Consumer<Pojo>)
+					} as Consumer<NetChannel<Pojo, Pojo>>).
+					get()
+
+			def client = new TcpClientSpec<Pojo, Pojo>(NettyTcpClient).
+					env(env2).dispatcher("sync").
+					codec(codec).
+					connect("localhost", port).
+					get()
+
+		when: 'the server is started'
+			server.start().await(5, TimeUnit.SECONDS)
+
+		and: "connection is established"
+			def connection = client.open().await(5, TimeUnit.SECONDS)
+			assertNotNull("Connection made successfully", connection)
+
+		and: "pojo is written"
+			data.each { Pojo item -> connection.sendAndForget(item) }
+			dataLatch.await(30, TimeUnit.SECONDS)
+
+		then: "everything went fine"
+			dataLatch.count == 0
+			client.close().await(5, TimeUnit.SECONDS)
+			server.shutdown().await(5, TimeUnit.SECONDS)
+
+		where:
+			data << [
+					[new Pojo('John')],
+					[new Pojo('John'), new Pojo("Jane")],
+					[new Pojo('John'), new Pojo("Jane"), new Pojo("Blah")],
+					(1..10).collect { new Pojo("Value_$it") }.toList(),
+			]
+	}
+
+	@Unroll
+	def "Server should be able to send POJO to client"(List<Pojo> data) {
+		given: "a TcpServer and TcpClient with JSON codec"
+			def dataLatch = new CountDownLatch(data.size())
+
+			final int port = SocketUtils.findAvailableTcpPort()
+
+			def consumerMock = Mock(Consumer) { data.size() * accept(_) }
+			def codec = new LengthFieldCodec(new JsonCodec(Pojo))
+
+			def server = new TcpServerSpec<Pojo, Pojo>(NettyTcpServer).
+					env(env1).dispatcher("sync").
+					listen(port).
+					codec(codec).
+					consume({ conn -> data.each { pojo -> conn.sendAndForget(pojo) } } as Consumer).
+					get()
+
+			def client = new TcpClientSpec<Pojo, Pojo>(NettyTcpClient).
+					env(env2).dispatcher("sync").
+					codec(codec).
+					connect("localhost", port).
+					get()
+
+		when: 'the server is started'
+			server.start().await(5, TimeUnit.SECONDS)
+
+		and: "connection is established"
+			client.open().
+					consume({ NetChannel conn ->
+						conn.consume({ Pojo pojo ->
+							dataLatch.countDown()
+							consumerMock.accept(pojo)
+						} as Consumer)
+					} as Consumer).
+					await(1, TimeUnit.SECONDS)
+
+		and: "data is being sent"
+			dataLatch.await(30, TimeUnit.SECONDS)
+
+		then: "everything went fine"
+			dataLatch.count == 0
+			client.close().await(5, TimeUnit.SECONDS)
+			server.shutdown().await(5, TimeUnit.SECONDS)
+
+		where:
+			data << [
+					[new Pojo('John')],
+					[new Pojo('John'), new Pojo("Jane")],
+					[new Pojo('John'), new Pojo("Jane"), new Pojo("Blah")],
+					(1..10).collect { new Pojo("Value_$it") }.toList(),
+			]
+	}
+
+	static class Pojo {
+		public Pojo() {}
+
+		public Pojo(String name) { this.name = name }
+		String name
+	}
+
+}
diff --git a/reactor-net/src/test/groovy/reactor/net/tcp/netty/NettyTcpServerSpec.groovy b/reactor-net/src/test/groovy/reactor/net/tcp/netty/NettyTcpServerSpec.groovy
new file mode 100644
index 0000000..083b627
--- /dev/null
+++ b/reactor-net/src/test/groovy/reactor/net/tcp/netty/NettyTcpServerSpec.groovy
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.netty
+
+import reactor.core.Environment
+import reactor.function.Consumer
+import reactor.function.Function
+import reactor.io.Buffer
+import reactor.io.encoding.PassThroughCodec
+import reactor.net.netty.tcp.NettyTcpServer
+import reactor.net.NetChannel
+import reactor.io.encoding.json.JsonCodec
+import reactor.net.tcp.spec.TcpServerSpec
+import spock.lang.Specification
+
+import java.nio.ByteBuffer
+import java.nio.channels.SocketChannel
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/**
+ * @author Jon Brisbin
+ */
+class NettyTcpServerSpec extends Specification {
+
+	static final int port = 26874
+	Environment env
+
+	def setup() {
+		env = new Environment()
+	}
+
+	def "NettyTcpServer responds to requests from clients"() {
+		given: "a simple TcpServer"
+		def startLatch = new CountDownLatch(1)
+		def stopLatch = new CountDownLatch(1)
+		def dataLatch = new CountDownLatch(1)
+		def server = new TcpServerSpec<Buffer, Buffer>(NettyTcpServer).
+				env(env).
+				listen(port).
+				codec(new PassThroughCodec<Buffer>()).
+				consume({ NetChannel<Buffer, Buffer> conn ->
+					conn.receive({ Buffer data ->
+						Buffer.wrap("Hello World!")
+					} as Function<Buffer, Buffer>)
+				} as Consumer<NetChannel<Buffer, Buffer>>).
+				get()
+
+		when: "the server is started"
+		server.start({
+			startLatch.countDown()
+		})
+		startLatch.await(5, TimeUnit.SECONDS)
+
+		then: "the server was started"
+		startLatch.count == 0
+
+		when: "data is sent"
+		def client = new SimpleClient(port, dataLatch, Buffer.wrap("Hello World!"))
+		client.start()
+		dataLatch.await(5, TimeUnit.SECONDS)
+
+		then: "data was recieved"
+		client.data?.remaining() == 12
+		new Buffer(client.data).asString() == "Hello World!"
+		dataLatch.count == 0
+
+		when: "the server is stopped"
+		server.shutdown().onSuccess({
+			stopLatch.countDown()
+		} as Consumer<Void>)
+		stopLatch.await(5, TimeUnit.SECONDS)
+
+		then: "the server was stopped"
+		stopLatch.count == 0
+	}
+
+	def "NettyTcpServer can encode and decode JSON"() {
+		given: "a TcpServer with JSON codec"
+		def startLatch = new CountDownLatch(1)
+		def stopLatch = new CountDownLatch(1)
+		def dataLatch = new CountDownLatch(1)
+		def server = new TcpServerSpec<Pojo, Pojo>(NettyTcpServer).
+				env(env).
+				listen(port).
+				codec(new JsonCodec<Pojo, Pojo>(Pojo)).
+				consume({ conn ->
+					conn.receive({ pojo ->
+						assert pojo.name == "John Doe"
+						new Pojo(name: "Jane Doe")
+					} as Function<Pojo, Pojo>)
+				} as Consumer<NetChannel<Pojo, Pojo>>).
+				get()
+
+		when: "the server is started"
+		server.start({
+			startLatch.countDown()
+		})
+		startLatch.await(5, TimeUnit.SECONDS)
+
+		then: "the server was started"
+		startLatch.count == 0
+
+		when: "a pojo is written"
+		def client = new SimpleClient(port, dataLatch, Buffer.wrap("{\"name\":\"John Doe\"}"))
+		client.start()
+		dataLatch.await(5, TimeUnit.SECONDS)
+
+		then: "data was recieved"
+		client.data?.remaining() == 19
+		new Buffer(client.data).asString() == "{\"name\":\"Jane Doe\"}"
+		dataLatch.count == 0
+
+		when: "the server is stopped"
+		server.shutdown().onSuccess({
+			stopLatch.countDown()
+		} as Consumer<Void>)
+		stopLatch.await(5, TimeUnit.SECONDS)
+
+		then: "the server was stopped"
+		stopLatch.count == 0
+	}
+
+	static class SimpleClient extends Thread {
+		final int port
+		final CountDownLatch latch
+		final Buffer output
+		ByteBuffer data
+
+		SimpleClient(int port, CountDownLatch latch, Buffer output) {
+			this.port = port
+			this.latch = latch
+			this.output = output
+		}
+
+		@Override
+		void run() {
+			def ch = SocketChannel.open(new InetSocketAddress(port))
+			def len = ch.write(output.byteBuffer())
+			assert ch.connected
+			data = ByteBuffer.allocate(len)
+			int read = ch.read(data)
+			assert read > 0
+			data.flip()
+			latch.countDown()
+		}
+	}
+
+	static class Pojo {
+		String name
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/AbstractNetClientServerTest.java b/reactor-net/src/test/java/reactor/net/AbstractNetClientServerTest.java
new file mode 100644
index 0000000..dacbd37
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/AbstractNetClientServerTest.java
@@ -0,0 +1,259 @@
+package reactor.net;
+
+import com.gs.collections.api.RichIterable;
+import com.gs.collections.impl.list.mutable.FastList;
+import org.junit.After;
+import org.junit.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+import reactor.function.Predicate;
+import reactor.io.Buffer;
+import reactor.io.encoding.Codec;
+import reactor.io.encoding.StandardCodecs;
+import reactor.net.tcp.TcpClient;
+import reactor.net.tcp.TcpServer;
+import reactor.net.tcp.spec.TcpClientSpec;
+import reactor.net.tcp.spec.TcpServerSpec;
+import reactor.net.tcp.support.SocketUtils;
+import reactor.support.NamedDaemonThreadFactory;
+
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Jon Brisbin
+ */
+public class AbstractNetClientServerTest {
+
+	public static final String LOCALHOST = "127.0.0.1";
+
+	private static final Random random = new Random(System.nanoTime());
+	private static final String CHARS  = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789";
+
+	protected final Logger log = LoggerFactory.getLogger(getClass());
+
+	private final int senderThreads = Environment.PROCESSORS;
+	protected Data            data;
+	private   ExecutorService serverPool;
+	private   ExecutorService clientPool;
+	private   Environment     env1;
+	private   Environment     env2;
+	private   int             port;
+
+	protected static Data generateData() {
+		char[] name = new char[16];
+		for (int i = 0; i < name.length; i++) {
+			name[i] = CHARS.charAt(random.nextInt(CHARS.length()));
+		}
+		return new Data(random.nextInt(), random.nextLong(), new String(name));
+	}
+
+	protected static <IN, OUT> NetChannel<IN, OUT> assertClientStarted(NetClient<IN, OUT> client)
+			throws InterruptedException {
+		NetChannel<IN, OUT> ch = client.open().await(1, TimeUnit.SECONDS);
+		assertNotNull(client.getClass().getSimpleName() + " was started", ch);
+		return ch;
+	}
+
+	protected static <IN, OUT> void assertClientStopped(NetClient<IN, OUT> client)
+			throws InterruptedException {
+		assertTrue(client.getClass().getSimpleName() + " was stopped", client.close().await(1, TimeUnit.SECONDS));
+	}
+
+
+	protected static <IN, OUT> void assertServerStarted(NetServer<IN, OUT> server) throws InterruptedException {
+		assertTrue(server.getClass().getSimpleName() + " was started", server.start().await(1, TimeUnit.SECONDS));
+	}
+
+	protected static <IN, OUT> void assertServerStopped(NetServer<IN, OUT> server) throws InterruptedException {
+		assertTrue(server.getClass().getSimpleName() + " was started", server.shutdown().await(1, TimeUnit.SECONDS));
+	}
+
+	@Before
+	public void setup() {
+		clientPool = Executors.newCachedThreadPool(new NamedDaemonThreadFactory(getClass().getSimpleName() + "-server"));
+		serverPool = Executors.newCachedThreadPool(new NamedDaemonThreadFactory(getClass().getSimpleName() + "-client"));
+
+		env1 = new Environment();
+		env2 = new Environment();
+
+		port = SocketUtils.findAvailableTcpPort();
+
+		data = generateData();
+	}
+
+	@After
+	public void cleanup() throws InterruptedException {
+		//env1.shutdown();
+		//env2.shutdown();
+
+		clientPool.shutdownNow();
+		clientPool.awaitTermination(5, TimeUnit.SECONDS);
+
+		serverPool.shutdownNow();
+		serverPool.awaitTermination(5, TimeUnit.SECONDS);
+	}
+
+	protected int getPort() {
+		return port;
+	}
+
+	protected <T> TcpServerSpec<T, T> createTcpServer(Class<? extends TcpServer> type,
+	                                                  Class<? extends T> dataType) {
+		return createTcpServer(type, dataType, dataType);
+	}
+
+	protected <IN, OUT> TcpServerSpec<IN, OUT> createTcpServer(Class<? extends TcpServer> type,
+	                                                           Class<? extends IN> inType,
+	                                                           Class<? extends OUT> outType) {
+		return new TcpServerSpec<IN, OUT>(type).env(env1).dispatcher("sync").listen(LOCALHOST, port);
+	}
+
+	protected <T> TcpClientSpec<T, T> createTcpClient(Class<? extends TcpClient> type,
+	                                                  Class<? extends T> dataType) {
+		return createTcpClient(type, dataType, dataType);
+	}
+
+	protected <IN, OUT> TcpClientSpec<IN, OUT> createTcpClient(Class<? extends TcpClient> type,
+	                                                           Class<? extends IN> inType,
+	                                                           Class<? extends OUT> outType) {
+		return new TcpClientSpec<IN, OUT>(type).env(env2).dispatcher("sync").connect(LOCALHOST, port);
+	}
+
+	protected <T> void assertTcpClientServerExchangedData(Class<? extends TcpServer> serverType,
+	                                                      Class<? extends TcpClient> clientType,
+	                                                      Buffer data) throws InterruptedException {
+		assertTcpClientServerExchangedData(
+				serverType,
+				clientType,
+				StandardCodecs.PASS_THROUGH_CODEC,
+				data,
+				(Buffer b) -> {
+					byte[] b1 = data.flip().asBytes();
+					byte[] b2 = b.asBytes();
+					return Arrays.equals(b1, b2);
+				}
+		);
+	}
+
+	@SuppressWarnings("unchecked")
+	protected <T> void assertTcpClientServerExchangedData(Class<? extends TcpServer> serverType,
+	                                                      Class<? extends TcpClient> clientType,
+	                                                      Codec<Buffer, T, T> codec,
+	                                                      T data,
+	                                                      Predicate<T> replyPredicate)
+			throws InterruptedException {
+		if (null == codec) {
+			codec = (Codec<Buffer, T, T>) StandardCodecs.PASS_THROUGH_CODEC;
+		}
+		TcpServer<T, T> server = new TcpServerSpec<T, T>(serverType)
+				.env(env1)
+				.listen(LOCALHOST, getPort())
+				.codec(codec)
+				.consume(ch -> ch.consume(ch::send))
+				.get();
+
+		assertServerStarted(server);
+
+		TcpClient<T, T> client = new TcpClientSpec<T, T>(clientType)
+				.env(env2)
+				.connect(LOCALHOST, getPort())
+				.codec(codec)
+				.get();
+
+		NetChannel<T, T> ch = assertClientStarted(client);
+
+		T reply = ch.sendAndReceive(data).await(1, TimeUnit.SECONDS);
+		assertTrue("reply was correct", replyPredicate.test(reply));
+
+//		assertServerStopped(server);
+//		assertClientStopped(client);
+	}
+
+	protected Environment getServerEnvironment() {
+		return env1;
+	}
+
+	protected Environment getClientEnvironment() {
+		return env2;
+	}
+
+	protected Future<?> submitServer(Runnable r) {
+		return serverPool.submit(r);
+	}
+
+	protected RichIterable<Future<?>> submitClients(Runnable r) {
+		FastList<Future<?>> futures = FastList.newList();
+		for (int i = 0; i < senderThreads; i++) {
+			futures.add(clientPool.submit(r));
+		}
+		return futures.toImmutable();
+	}
+
+	protected static class Data {
+		private int    count;
+		private long   length;
+		private String name;
+
+		public Data() {
+		}
+
+		public Data(int count, long length, String name) {
+			this.count = count;
+			this.length = length;
+			this.name = name;
+		}
+
+		public int getCount() {
+			return count;
+		}
+
+		public long getLength() {
+			return length;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		@Override
+		public String toString() {
+			return "Data{" +
+					"count=" + count +
+					", length=" + length +
+					", name='" + name + '\'' +
+					'}';
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (this == o) return true;
+			if (!(o instanceof Data)) return false;
+
+			Data data = (Data) o;
+
+			if (count != data.count) return false;
+			if (length != data.length) return false;
+			if (!name.equals(data.name)) return false;
+
+			return true;
+		}
+
+		@Override
+		public int hashCode() {
+			int result = count;
+			result = 31 * result + (int) (length ^ (length >>> 32));
+			result = 31 * result + name.hashCode();
+			return result;
+		}
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/IncrementalBackoffReconnectTest.java b/reactor-net/src/test/java/reactor/net/tcp/IncrementalBackoffReconnectTest.java
new file mode 100644
index 0000000..d32fe72
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/IncrementalBackoffReconnectTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp;
+
+
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.net.Reconnect;
+import reactor.net.tcp.spec.IncrementalBackoffReconnectSpec;
+import reactor.tuple.Tuple2;
+
+import java.net.InetSocketAddress;
+
+import static org.junit.Assert.assertEquals;
+
+ at Ignore
+public class IncrementalBackoffReconnectTest {
+    @Test
+    public void testDefaultReconnect() {
+        Reconnect rec = new IncrementalBackoffReconnectSpec().get();
+
+        InetSocketAddress a1 = new InetSocketAddress("129.168.0.1",1001);
+        Tuple2<InetSocketAddress, Long> t1 = rec.reconnect(a1, 0);
+
+        assertEquals(IncrementalBackoffReconnectSpec.DEFAULT_INTERVAL,t1.getT2().longValue());
+        assertEquals(a1,t1.getT1());
+
+        InetSocketAddress a2 = new InetSocketAddress("129.168.0.1",1001);
+        Tuple2<InetSocketAddress, Long> t2 = rec.reconnect(a1, 0);
+
+        assertEquals(IncrementalBackoffReconnectSpec.DEFAULT_INTERVAL,t2.getT2().longValue());
+        assertEquals(a2,t2.getT1());
+    }
+
+    @Test
+    public void testReconnectIntervalWithCap() {
+        InetSocketAddress addr1 = new InetSocketAddress("129.168.0.1",1001);
+
+        Reconnect rec = new IncrementalBackoffReconnectSpec()
+            .address(addr1)
+            .interval(5000)
+            .maxInterval(10000)
+            .multiplier(2)
+            .get();
+
+        assertEquals(    0L,(long)rec.reconnect(addr1,0).getT2());
+        assertEquals( 5000L,(long)rec.reconnect(addr1,1).getT2());
+        assertEquals(10000L,(long)rec.reconnect(addr1,2).getT2());
+        assertEquals(10000L,(long)rec.reconnect(addr1,3).getT2());
+    }
+
+    @Test
+    public void testRoundRobinAddresses() {
+        InetSocketAddress addr1 = new InetSocketAddress("129.168.0.1",1001);
+        InetSocketAddress addr2 = new InetSocketAddress("129.168.0.2",1002);
+        InetSocketAddress addr3 = new InetSocketAddress("129.168.0.3",1003);
+
+        Reconnect rec = new IncrementalBackoffReconnectSpec()
+            .address(addr1)
+            .address(addr2)
+            .address(addr3)
+            .get();
+
+        assertEquals(addr1,rec.reconnect(addr1,0).getT1());
+        assertEquals(addr2,rec.reconnect(addr2,1).getT1());
+        assertEquals(addr3,rec.reconnect(addr3,2).getT1());
+    }
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/SpeedTests.java b/reactor-net/src/test/java/reactor/net/tcp/SpeedTests.java
new file mode 100644
index 0000000..0756c7d
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/SpeedTests.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Jon Brisbin
+ */
+public class SpeedTests {
+
+	final static int runs       = 100000000;
+	final static int iterations = 3;
+
+	@Test
+	@Ignore
+	public void clockIntegerParseInt() {
+		Consumer<Integer> test = new Consumer<Integer>() {
+			byte[] bytes = "34".getBytes();
+
+			@Override
+			public void accept(Integer integer) {
+				String s = new String(bytes);
+				Integer.parseInt(s);
+			}
+		};
+
+		doTest("parseInt", test);
+	}
+
+	@Test
+	@Ignore
+	public void clockByteBufferGetInt() {
+		final Buffer b = Buffer.wrap("34");
+
+		Consumer<Integer> test = new Consumer<Integer>() {
+			@Override
+			public void accept(Integer integer) {
+				Buffer.parseInt(b);
+			}
+		};
+
+		doTest("char shifting", test);
+	}
+
+	@Test
+	@Ignore
+	public void clockSimpleDateFormatParse() {
+		final SimpleDateFormat rfc3414 = new SimpleDateFormat("MMM d HH:mm:ss");
+		final byte[] bytes = "Oct 11 22:14:15".getBytes();
+
+		Consumer<Integer> test = new Consumer<Integer>() {
+			@Override
+			public void accept(Integer integer) {
+				try {
+					rfc3414.parse(new String(bytes));
+				} catch (ParseException e) {
+					throw new IllegalStateException(e);
+				}
+			}
+		};
+
+		doTest("RFC3414 parse", test);
+	}
+
+	@Test
+	@Ignore
+	public void clockManualDateExtraction() {
+		final String[] months = new String[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+		final Buffer date = Buffer.wrap("Oct 11 22:14:15");
+		final Iterable<Buffer.View> slices = date.slice(0, 3, 4, 6, 7, 9, 10, 12, 13);
+
+		Consumer<Integer> test = new Consumer<Integer>() {
+			Calendar cal = Calendar.getInstance();
+
+			@Override
+			public void accept(Integer integer) {
+				Iterator<Buffer.View> iter = slices.iterator();
+
+				Buffer b = iter.next().get();
+				int month = Arrays.binarySearch(months, b.asString());
+				int day = Buffer.parseInt(iter.next().get());
+				int hr = Buffer.parseInt(iter.next().get());
+				int min = Buffer.parseInt(iter.next().get());
+				int sec = Buffer.parseInt(iter.next().get());
+
+				cal.set(2013, month, day, hr, min, sec);
+				cal.getTime();
+			}
+		};
+
+		doTest("slice parse", test);
+	}
+
+	@Test
+	@Ignore
+	public void clockRegexExtraction() {
+		final String[] months = new String[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+		final Pattern pattern = Pattern.compile("([A-Z][a-z]{2})[\\s]+(\\d{1,2})[\\s](\\d{2}):(\\d{2}):(\\d{2})");
+		final Buffer date = Buffer.wrap("Oct 11 22:14:15");
+
+		Consumer<Integer> test = new Consumer<Integer>() {
+			Calendar cal = Calendar.getInstance();
+
+			@Override
+			public void accept(Integer integer) {
+				Matcher m = pattern.matcher(date.asString());
+				m.matches();
+
+				int month = Arrays.binarySearch(months, m.group(1));
+				int day = Integer.valueOf(m.group(2));
+				int hr = Integer.valueOf(m.group(2));
+				int min = Integer.valueOf(m.group(2));
+				int sec = Integer.valueOf(m.group(2));
+
+				cal.set(2013, month, day, hr, min, sec);
+				cal.getTime();
+			}
+		};
+
+		doTest("regex parse", test);
+	}
+
+	private void doTest(String name, Consumer<Integer> test) {
+		for (int j = 0; j < iterations; j++) {
+			int runs = SpeedTests.runs / 100;
+			long start = System.currentTimeMillis();
+			for (int i = 0; i < runs; i++) {
+				test.accept(i);
+			}
+			long end = System.currentTimeMillis();
+			long elapsed = end - start;
+			int throughput = (int) ((runs * 1.0) / ((elapsed * 1.0) / 1000));
+
+			System.out.println(name + " (run #" + (j + 1) + "): \t" + throughput + "/sec");
+		}
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/TcpClientTests.java b/reactor-net/src/test/java/reactor/net/tcp/TcpClientTests.java
new file mode 100644
index 0000000..d2cd5ee
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/TcpClientTests.java
@@ -0,0 +1,591 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp;
+
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.codec.http.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.core.Environment;
+import reactor.function.Consumer;
+import reactor.function.batch.BatchConsumer;
+import reactor.io.Buffer;
+import reactor.io.encoding.StandardCodecs;
+import reactor.net.NetChannel;
+import reactor.net.NetClient;
+import reactor.net.NetServer;
+import reactor.net.Reconnect;
+import reactor.net.netty.NettyClientSocketOptions;
+import reactor.net.netty.tcp.NettyTcpClient;
+import reactor.net.tcp.spec.TcpClientSpec;
+import reactor.net.tcp.spec.TcpServerSpec;
+import reactor.net.tcp.support.SocketUtils;
+import reactor.net.zmq.tcp.ZeroMQTcpClient;
+import reactor.net.zmq.tcp.ZeroMQTcpServer;
+import reactor.tuple.Tuple;
+import reactor.tuple.Tuple2;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author Jon Brisbin
+ */
+ at Ignore
+public class TcpClientTests {
+
+	private final ExecutorService threadPool = Executors.newCachedThreadPool();
+	Environment             env;
+	int                     echoServerPort;
+	EchoServer              echoServer;
+	int                     abortServerPort;
+	ConnectionAbortServer   abortServer;
+	int                     timeoutServerPort;
+	ConnectionTimeoutServer timeoutServer;
+	int                     heartbeatServerPort;
+	HeartbeatServer         heartbeatServer;
+
+	@Before
+	public void setup() {
+		env = new Environment();
+
+		echoServerPort = SocketUtils.findAvailableTcpPort();
+		echoServer = new EchoServer(echoServerPort);
+		threadPool.submit(echoServer);
+
+		abortServerPort = SocketUtils.findAvailableTcpPort();
+		abortServer = new ConnectionAbortServer(abortServerPort);
+		threadPool.submit(abortServer);
+
+		timeoutServerPort = SocketUtils.findAvailableTcpPort();
+		timeoutServer = new ConnectionTimeoutServer(timeoutServerPort);
+		threadPool.submit(timeoutServer);
+
+		heartbeatServerPort = SocketUtils.findAvailableTcpPort();
+		heartbeatServer = new HeartbeatServer(heartbeatServerPort);
+		threadPool.submit(heartbeatServer);
+	}
+
+	@After
+	public void cleanup() throws InterruptedException, IOException {
+		echoServer.close();
+		abortServer.close();
+		timeoutServer.close();
+		heartbeatServer.close();
+		threadPool.shutdown();
+		threadPool.awaitTermination(5, TimeUnit.SECONDS);
+		Thread.sleep(500);
+	}
+
+	@Test
+	public void testTcpClient() throws InterruptedException {
+		final CountDownLatch latch = new CountDownLatch(1);
+
+		TcpClient<String, String> client = new TcpClientSpec<String, String>(NettyTcpClient.class)
+				.env(env)
+				.codec(StandardCodecs.STRING_CODEC)
+				.connect("localhost", echoServerPort)
+				.get();
+
+		client.open().consume(new Consumer<NetChannel<String, String>>() {
+			@Override
+			public void accept(NetChannel<String, String> conn) {
+				conn.in().consume(new Consumer<String>() {
+					@Override
+					public void accept(String s) {
+						latch.countDown();
+					}
+				});
+				conn.out().accept("Hello World!");
+			}
+		});
+
+		latch.await(30, TimeUnit.SECONDS);
+
+		client.close();
+
+		assertThat("latch was counted down", latch.getCount(), is(0L));
+	}
+
+	@Test
+	public void testTcpClientWithInetSocketAddress() throws InterruptedException {
+		final CountDownLatch latch = new CountDownLatch(1);
+
+		TcpClient<String, String> client = new TcpClientSpec<String, String>(NettyTcpClient.class)
+				.env(env)
+				.codec(StandardCodecs.STRING_CODEC)
+				.connect(new InetSocketAddress(echoServerPort))
+				.get();
+
+		client.open().consume(new Consumer<NetChannel<String, String>>() {
+			@Override
+			public void accept(NetChannel<String, String> conn) {
+				conn.in().consume(new Consumer<String>() {
+					@Override
+					public void accept(String s) {
+						latch.countDown();
+					}
+				});
+				conn.out().accept("Hello World!");
+			}
+		});
+
+		latch.await(30, TimeUnit.SECONDS);
+
+		client.close();
+
+		assertThat("latch was counted down", latch.getCount(), is(0L));
+	}
+
+	@Test
+	public void tcpClientHandlesLineFeedData() throws InterruptedException {
+		final int messages = 100;
+		final CountDownLatch latch = new CountDownLatch(messages);
+		final List<String> strings = new ArrayList<String>();
+
+		TcpClient<String, String> client = new TcpClientSpec<String, String>(NettyTcpClient.class)
+				.env(env)
+				.codec(StandardCodecs.LINE_FEED_CODEC)
+				.connect("localhost", echoServerPort)
+				.get();
+
+		client.open().consume(new Consumer<NetChannel<String, String>>() {
+			@Override
+			public void accept(NetChannel<String, String> conn) {
+				conn.in().consume(new Consumer<String>() {
+					@Override
+					public void accept(String s) {
+						strings.add(s);
+						latch.countDown();
+					}
+				});
+
+				BatchConsumer<String> out = conn.out();
+				out.start();
+				for (int i = 0; i < messages; i++) {
+					out.accept("Hello World!");
+				}
+				out.end();
+			}
+		});
+
+		assertTrue("Expected messages not received. Received " + strings.size() + " messages: " + strings,
+		           latch.await(5, TimeUnit.SECONDS));
+		client.close();
+
+		assertEquals(messages, strings.size());
+		Set<String> uniqueStrings = new HashSet<String>(strings);
+		assertEquals(1, uniqueStrings.size());
+		assertEquals("Hello World!", uniqueStrings.iterator().next());
+	}
+
+	@Test
+	public void closingPromiseIsFulfilled() throws InterruptedException {
+		TcpClient<String, String> client = new TcpClientSpec<String, String>(NettyTcpClient.class)
+				.env(env)
+				.codec(null)
+				.connect("localhost", echoServerPort)
+				.get();
+
+		assertTrue("Client was not closed within 30 seconds", client.close().await(30, TimeUnit.SECONDS));
+	}
+
+	@Test
+	public void connectionWillRetryConnectionAttemptWhenItFails() throws InterruptedException {
+		final CountDownLatch latch = new CountDownLatch(1);
+		final AtomicLong totalDelay = new AtomicLong();
+
+		new TcpClientSpec<Buffer, Buffer>(NettyTcpClient.class)
+				.env(env)
+				.connect("localhost", abortServerPort + 3)
+				.get()
+				.open(new Reconnect() {
+					@Override
+					public Tuple2<InetSocketAddress, Long> reconnect(InetSocketAddress currentAddress, int attempt) {
+						switch (attempt) {
+							case 1:
+								totalDelay.addAndGet(100);
+								return Tuple.of(currentAddress, 100L);
+							case 2:
+								totalDelay.addAndGet(500);
+								return Tuple.of(currentAddress, 500L);
+							case 3:
+								totalDelay.addAndGet(1000);
+								return Tuple.of(currentAddress, 1000L);
+							default:
+								latch.countDown();
+								return null;
+						}
+					}
+				});
+
+		assertTrue("latch was counted down", latch.await(5, TimeUnit.SECONDS));
+		assertThat("totalDelay was >1.6s", totalDelay.get(), greaterThanOrEqualTo(1600L));
+	}
+
+	@Test
+	public void connectionWillAttemptToReconnectWhenItIsDropped() throws InterruptedException, IOException {
+		final CountDownLatch connectionLatch = new CountDownLatch(1);
+		final CountDownLatch reconnectionLatch = new CountDownLatch(1);
+		new TcpClientSpec<Buffer, Buffer>(NettyTcpClient.class)
+				.env(env)
+				.connect("localhost", abortServerPort)
+				.get()
+				.open(new Reconnect() {
+					@Override
+					public Tuple2<InetSocketAddress, Long> reconnect(InetSocketAddress currentAddress, int attempt) {
+						reconnectionLatch.countDown();
+						return null;
+					}
+				})
+				.consume(new Consumer<NetChannel<Buffer, Buffer>>() {
+					@Override
+					public void accept(NetChannel<Buffer, Buffer> connection) {
+						connectionLatch.countDown();
+					}
+				});
+
+		assertTrue("Initial connection is made", connectionLatch.await(5, TimeUnit.SECONDS));
+		assertTrue("A reconnect attempt was made", reconnectionLatch.await(5, TimeUnit.SECONDS));
+	}
+
+	@Test
+	public void consumerSpecAssignsEventHandlers() throws InterruptedException {
+		final CountDownLatch latch = new CountDownLatch(3);
+		final AtomicLong totalDelay = new AtomicLong();
+		final long start = System.currentTimeMillis();
+
+		new TcpClientSpec<Buffer, Buffer>(NettyTcpClient.class)
+				.env(env)
+				.connect("localhost", timeoutServerPort)
+				.get().open().await(5, TimeUnit.SECONDS).on()
+				.close(new Runnable() {
+					@Override
+					public void run() {
+						latch.countDown();
+					}
+				})
+				.readIdle(500, new Runnable() {
+					@Override
+					public void run() {
+						totalDelay.addAndGet(System.currentTimeMillis() - start);
+						latch.countDown();
+					}
+				})
+				.writeIdle(500, new Runnable() {
+					@Override
+					public void run() {
+						totalDelay.addAndGet(System.currentTimeMillis() - start);
+						latch.countDown();
+					}
+				});
+
+		assertTrue("latch was counted down", latch.await(5, TimeUnit.SECONDS));
+		assertThat("totalDelay was >500ms", totalDelay.get(), greaterThanOrEqualTo(500L));
+	}
+
+	@Test
+	public void readIdleDoesNotFireWhileDataIsBeingRead() throws InterruptedException, IOException {
+		final CountDownLatch latch = new CountDownLatch(1);
+		long start = System.currentTimeMillis();
+
+		new TcpClientSpec<Buffer, Buffer>(NettyTcpClient.class)
+				.env(env)
+				.connect("localhost", heartbeatServerPort)
+				.get().open().await().on()
+				.readIdle(500, new Runnable() {
+					@Override
+					public void run() {
+						latch.countDown();
+					}
+				});
+
+		Thread.sleep(700);
+		heartbeatServer.close();
+
+		assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+		long duration = System.currentTimeMillis() - start;
+
+		assertThat(duration, is(greaterThanOrEqualTo(1000L)));
+	}
+
+	@Test
+	public void writeIdleDoesNotFireWhileDataIsBeingSent() throws InterruptedException, IOException {
+		final CountDownLatch latch = new CountDownLatch(1);
+		long start = System.currentTimeMillis();
+
+		NetChannel<Buffer, Buffer> connection = new TcpClientSpec<Buffer, Buffer>(NettyTcpClient.class)
+				.env(env)
+				.connect("localhost", echoServerPort)
+				.get().open().await();
+
+		connection.on()
+		          .writeIdle(500, new Runnable() {
+			          @Override
+			          public void run() {
+				          latch.countDown();
+			          }
+		          });
+
+		for (int i = 0; i < 5; i++) {
+			Thread.sleep(100);
+			connection.sendAndForget(Buffer.wrap("a"));
+		}
+
+		assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+		long duration = System.currentTimeMillis() - start;
+
+		assertThat(duration, is(greaterThanOrEqualTo(1000L)));
+	}
+
+	@Test
+	public void nettyNetChannelAcceptsNettyChannelHandlers() throws InterruptedException {
+		NetChannel<HttpObject, HttpRequest> connection =
+				new TcpClientSpec<HttpObject, HttpRequest>(NettyTcpClient.class)
+						.env(env)
+						.options(new NettyClientSocketOptions()
+								         .pipelineConfigurer(new Consumer<ChannelPipeline>() {
+									         @Override
+									         public void accept(ChannelPipeline pipeline) {
+										         pipeline.addLast(new HttpClientCodec());
+									         }
+								         }))
+						.connect("www.google.com", 80)
+						.get().open().await();
+
+		final CountDownLatch latch = new CountDownLatch(1);
+		connection.sendAndReceive(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"))
+		          .onSuccess(new Consumer<HttpObject>() {
+			          @Override
+			          public void accept(HttpObject resp) {
+				          latch.countDown();
+				          System.out.println("resp: " + resp);
+			          }
+		          });
+
+		assertTrue("Latch didn't time out", latch.await(15, TimeUnit.SECONDS));
+	}
+
+	@Test
+	public void zmqClientServerInteraction() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+		final CountDownLatch latch = new CountDownLatch(2);
+
+		NetServer<Buffer, Buffer> zmqs = new TcpServerSpec<Buffer, Buffer>(ZeroMQTcpServer.class)
+				.env(env)
+				.listen(port)
+				.consume(ch -> {
+					ch.consume(buff -> {
+						if (buff.remaining() == 12) {
+							latch.countDown();
+							ch.sendAndForget(Buffer.wrap("Goodbye World!"));
+						}
+					});
+				})
+				.get();
+
+		assertTrue("server was started", zmqs.start().await(5, TimeUnit.SECONDS));
+
+		NetClient<Buffer, Buffer> zmqc = new TcpClientSpec<Buffer, Buffer>(ZeroMQTcpClient.class)
+				.env(env)
+				.connect("127.0.0.1", port)
+				.get();
+
+		NetChannel<Buffer, Buffer> ch = zmqc.open().await(5, TimeUnit.SECONDS);
+		assertNotNull("channel was connected", ch);
+
+		String msg = ch.sendAndReceive(Buffer.wrap("Hello World!"))
+		               .await(5, TimeUnit.SECONDS)
+		               .asString();
+
+		assertThat("messages were exchanged", msg, is("Goodbye World!"));
+	}
+
+	private static final class EchoServer implements Runnable {
+		private final    int                 port;
+		private volatile ServerSocketChannel server;
+		private volatile Thread              thread;
+
+		private EchoServer(int port) {
+			this.port = port;
+		}
+
+		@Override
+		public void run() {
+			try {
+				server = ServerSocketChannel.open();
+				server.socket().bind(new InetSocketAddress(port));
+				server.configureBlocking(true);
+				thread = Thread.currentThread();
+				while (true) {
+					SocketChannel ch = server.accept();
+
+					ByteBuffer buffer = ByteBuffer.allocate(Buffer.SMALL_BUFFER_SIZE);
+					while (true) {
+						int read = ch.read(buffer);
+						if (read > 0) {
+							buffer.flip();
+						}
+
+						int written = ch.write(buffer);
+						if (written < 0) {
+							throw new IOException("Cannot write to client");
+						}
+						buffer.rewind();
+					}
+				}
+			} catch (IOException e) {
+				// Server closed
+			}
+		}
+
+		public void close() throws IOException {
+			Thread thread = this.thread;
+			if (thread != null) {
+				thread.interrupt();
+			}
+			ServerSocketChannel server = this.server;
+			if (server != null) {
+				server.close();
+			}
+		}
+	}
+
+	private static final class ConnectionAbortServer implements Runnable {
+		final            int                 port;
+		private volatile ServerSocketChannel server;
+
+		private ConnectionAbortServer(int port) {
+			this.port = port;
+		}
+
+		@Override
+		public void run() {
+			try {
+				server = ServerSocketChannel.open();
+				server.socket().bind(new InetSocketAddress(port));
+				server.configureBlocking(true);
+				while (true) {
+					SocketChannel ch = server.accept();
+					ch.close();
+				}
+			} catch (Exception e) {
+				// Server closed
+			}
+		}
+
+		public void close() throws IOException {
+			ServerSocketChannel server = this.server;
+			if (server != null) {
+				server.close();
+			}
+		}
+	}
+
+	private static final class ConnectionTimeoutServer implements Runnable {
+		final            int                 port;
+		private volatile ServerSocketChannel server;
+
+		private ConnectionTimeoutServer(int port) {
+			this.port = port;
+		}
+
+		@Override
+		public void run() {
+			try {
+				server = ServerSocketChannel.open();
+				server.socket().bind(new InetSocketAddress(port));
+				server.configureBlocking(true);
+				while (true) {
+					SocketChannel ch = server.accept();
+					ByteBuffer buff = ByteBuffer.allocate(1);
+					ch.read(buff);
+				}
+			} catch (IOException e) {
+				// Server closed
+			}
+		}
+
+		public void close() throws IOException {
+			ServerSocketChannel server = this.server;
+			if (server != null) {
+				server.close();
+			}
+		}
+	}
+
+	private static final class HeartbeatServer implements Runnable {
+		final            int                 port;
+		private volatile ServerSocketChannel server;
+
+		private HeartbeatServer(int port) {
+			this.port = port;
+		}
+
+		@Override
+		public void run() {
+			try {
+				server = ServerSocketChannel.open();
+				server.socket().bind(new InetSocketAddress(port));
+				server.configureBlocking(true);
+				while (true) {
+					SocketChannel ch = server.accept();
+					while (server.isOpen()) {
+						ByteBuffer out = ByteBuffer.allocate(1);
+						out.put((byte) '\n');
+						out.flip();
+						ch.write(out);
+						Thread.sleep(100);
+					}
+				}
+			} catch (IOException e) {
+				// Server closed
+			} catch (InterruptedException ie) {
+
+			}
+		}
+
+		public void close() throws IOException {
+			ServerSocketChannel server = this.server;
+			if (server != null) {
+				server.close();
+			}
+		}
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/TcpServerTests.java b/reactor-net/src/test/java/reactor/net/tcp/TcpServerTests.java
new file mode 100644
index 0000000..7c1b3ac
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/TcpServerTests.java
@@ -0,0 +1,674 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufProcessor;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.codec.LineBasedFrameDecoder;
+import io.netty.handler.codec.http.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.zeromq.ZContext;
+import org.zeromq.ZMQ;
+import org.zeromq.ZMsg;
+import reactor.core.Environment;
+import reactor.event.dispatch.SynchronousDispatcher;
+import reactor.function.Consumer;
+import reactor.function.Supplier;
+import reactor.io.Buffer;
+import reactor.io.encoding.*;
+import reactor.io.encoding.json.JsonCodec;
+import reactor.net.NetChannel;
+import reactor.net.NetServer;
+import reactor.net.config.ServerSocketOptions;
+import reactor.net.config.SslOptions;
+import reactor.net.netty.NettyServerSocketOptions;
+import reactor.net.netty.tcp.NettyTcpClient;
+import reactor.net.netty.tcp.NettyTcpServer;
+import reactor.net.tcp.spec.TcpClientSpec;
+import reactor.net.tcp.spec.TcpServerSpec;
+import reactor.net.tcp.support.SocketUtils;
+import reactor.net.zmq.ZeroMQServerSocketOptions;
+import reactor.net.zmq.tcp.ZeroMQTcpServer;
+import reactor.util.UUIDUtils;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Jon Brisbin
+ */
+ at Ignore
+public class TcpServerTests {
+
+	final Logger          log        = LoggerFactory.getLogger(TcpServerTests.class);
+	final ExecutorService threadPool = Executors.newCachedThreadPool();
+	final int             msgs       = 150;
+	final int             threads    = 4;
+
+	Environment    env;
+	CountDownLatch latch;
+	AtomicLong count = new AtomicLong();
+	AtomicLong start = new AtomicLong();
+	AtomicLong end   = new AtomicLong();
+
+	@Before
+	public void loadEnv() {
+		env = new Environment();
+		latch = new CountDownLatch(msgs * threads);
+	}
+
+	@After
+	public void cleanup() {
+		threadPool.shutdownNow();
+	}
+
+	@Test
+	public void tcpServerHandlesJsonPojosOverSsl() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+		SslOptions serverOpts = new SslOptions()
+				.keystoreFile("./src/test/resources/server.jks")
+				.keystorePasswd("changeit");
+
+		SslOptions clientOpts = new SslOptions()
+				.keystoreFile("./src/test/resources/client.jks")
+				.keystorePasswd("changeit")
+				.trustManagers(new Supplier<TrustManager[]>() {
+					@Override
+					public TrustManager[] get() {
+						return new TrustManager[]{
+								new X509TrustManager() {
+									@Override
+									public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
+											throws CertificateException {
+										// trust all
+									}
+
+									@Override
+									public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
+											throws CertificateException {
+										// trust all
+									}
+
+									@Override
+									public X509Certificate[] getAcceptedIssuers() {
+										return new X509Certificate[0];
+									}
+								}
+						};
+					}
+				});
+
+		JsonCodec<Pojo, Pojo> codec = new JsonCodec<Pojo, Pojo>(Pojo.class);
+
+		final CountDownLatch latch = new CountDownLatch(1);
+		final TcpClient<Pojo, Pojo> client = new TcpClientSpec<Pojo, Pojo>(NettyTcpClient.class)
+				.env(env)
+				.ssl(clientOpts)
+				.codec(codec)
+				.connect("localhost", port)
+				.get();
+
+		TcpServer<Pojo, Pojo> server = new TcpServerSpec<Pojo, Pojo>(NettyTcpServer.class)
+				.env(env)
+				.ssl(serverOpts)
+				.listen("localhost", port)
+				.codec(codec)
+				.consume(new Consumer<NetChannel<Pojo, Pojo>>() {
+					@Override
+					public void accept(NetChannel<Pojo, Pojo> ch) {
+						ch.consume(new Consumer<Pojo>() {
+							@Override
+							public void accept(Pojo data) {
+								if ("John Doe".equals(data.getName())) {
+									latch.countDown();
+								}
+							}
+						});
+					}
+				})
+				.get();
+
+		server.start().await();
+		client.open().await().send(new Pojo("John Doe"));
+
+		assertTrue("Latch was counted down", latch.await(5, TimeUnit.SECONDS));
+
+		client.close().await();
+		server.shutdown().await();
+	}
+
+	@Test
+	public void tcpServerHandlesLengthFieldData() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+
+		TcpServer<byte[], byte[]> server = new TcpServerSpec<byte[], byte[]>(NettyTcpServer.class)
+				.env(env)
+				.synchronousDispatcher()
+				.options(new ServerSocketOptions()
+						         .backlog(1000)
+						         .reuseAddr(true)
+						         .tcpNoDelay(true))
+				.listen(port)
+				.codec(new LengthFieldCodec<byte[], byte[]>(StandardCodecs.BYTE_ARRAY_CODEC))
+				.consume(new Consumer<NetChannel<byte[], byte[]>>() {
+					@Override
+					public void accept(NetChannel<byte[], byte[]> ch) {
+						ch.consume(new Consumer<byte[]>() {
+							long num = 1;
+
+							@Override
+							public void accept(byte[] bytes) {
+								latch.countDown();
+								ByteBuffer bb = ByteBuffer.wrap(bytes);
+								if (bb.remaining() < 4) {
+									System.err.println("insufficient len: " + bb.remaining());
+								}
+								int next = bb.getInt();
+								if (next != num++) {
+									System.err.println(this + " expecting: " + next + " but got: " + (num - 1));
+								}
+							}
+						});
+					}
+				})
+				.get();
+
+		server.start().await();
+
+		start.set(System.currentTimeMillis());
+		for (int i = 0; i < threads; i++) {
+			threadPool.submit(new LengthFieldMessageWriter(port));
+		}
+
+		assertTrue("Latch was counted down", latch.await(10, TimeUnit.SECONDS));
+		end.set(System.currentTimeMillis());
+
+		double elapsed = (end.get() - start.get()) * 1.0;
+		System.out.println("elapsed: " + (int) elapsed + "ms");
+		System.out.println("throughput: " + (int) ((msgs * threads) / (elapsed / 1000)) + "/sec");
+
+		server.shutdown().await();
+	}
+
+	@Test
+	public void tcpServerHandlesFrameData() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+
+		TcpServer<Frame, Frame> server = new TcpServerSpec<Frame, Frame>(NettyTcpServer.class)
+				.env(env)
+				.synchronousDispatcher()
+				.options(new ServerSocketOptions()
+						         .backlog(1000)
+						         .reuseAddr(true)
+						         .tcpNoDelay(true))
+				.listen(port)
+				.codec(new FrameCodec(2, FrameCodec.LengthField.SHORT))
+				.consume(new Consumer<NetChannel<Frame, Frame>>() {
+					@Override
+					public void accept(NetChannel<Frame, Frame> ch) {
+						ch.consume(new Consumer<Frame>() {
+							@Override
+							public void accept(Frame frame) {
+								short prefix = frame.getPrefix().readShort();
+								assertThat("prefix is 0", prefix == 0);
+								Buffer data = frame.getData();
+								assertThat("len is 128", data.remaining() == 128);
+
+								latch.countDown();
+							}
+						});
+					}
+				})
+				.get();
+
+		server.start().await();
+
+		start.set(System.currentTimeMillis());
+		for (int i = 0; i < threads; i++) {
+			threadPool.submit(new FramedLengthFieldMessageWriter(port));
+		}
+
+		assertTrue("Latch was counted down", latch.await(60, TimeUnit.SECONDS));
+		end.set(System.currentTimeMillis());
+
+		double elapsed = (end.get() - start.get()) * 1.0;
+		System.out.println("elapsed: " + (int) elapsed + "ms");
+		System.out.println("throughput: " + (int) ((msgs * threads) / (elapsed / 1000)) + "/sec");
+
+		server.shutdown().await();
+	}
+
+	@Test
+	public void exposesRemoteAddress() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+		final CountDownLatch latch = new CountDownLatch(1);
+
+		TcpClient<Buffer, Buffer> client = new TcpClientSpec<Buffer, Buffer>(NettyTcpClient.class)
+				.env(env)
+				.synchronousDispatcher()
+				.connect("localhost", port)
+				.uncaughtErrorHandler(t -> {}) // ignore channel errors
+				.get();
+
+		TcpServer<Buffer, Buffer> server = new TcpServerSpec<Buffer, Buffer>(NettyTcpServer.class)
+				.env(env)
+				.synchronousDispatcher()
+				.listen(port)
+				.codec(new PassThroughCodec<Buffer>())
+				.consume(new Consumer<NetChannel<Buffer, Buffer>>() {
+					@Override
+					public void accept(NetChannel<Buffer, Buffer> ch) {
+						InetSocketAddress remoteAddr = ch.remoteAddress();
+						assertNotNull("remote address is not null", remoteAddr.getAddress());
+						latch.countDown();
+					}
+				})
+				.get();
+
+		server.start().await();
+
+		NetChannel<Buffer, Buffer> out = client.open().await();
+		out.send(Buffer.wrap("Hello World!"));
+
+		assertTrue("latch was counted down", latch.await(5, TimeUnit.SECONDS));
+
+		server.shutdown().await();
+	}
+
+	@Test
+	public void exposesNettyPipelineConfiguration() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+		final CountDownLatch latch = new CountDownLatch(2);
+
+		final TcpClient<String, String> client = new TcpClientSpec<String, String>(NettyTcpClient.class)
+				.env(env)
+				.connect("localhost", port)
+				.codec(StandardCodecs.LINE_FEED_CODEC)
+				.get();
+
+		Consumer<NetChannel<String, String>> serverHandler = new Consumer<NetChannel<String, String>>() {
+			@Override
+			public void accept(NetChannel<String, String> ch) {
+				ch.consume(new Consumer<String>() {
+					@Override
+					public void accept(String data) {
+						latch.countDown();
+					}
+				});
+			}
+		};
+
+		TcpServer<String, String> server = new TcpServerSpec<String, String>(NettyTcpServer.class)
+				.env(env)
+				.options(new NettyServerSocketOptions()
+						         .pipelineConfigurer(new Consumer<ChannelPipeline>() {
+							         @Override
+							         public void accept(ChannelPipeline pipeline) {
+								         pipeline.addLast(new LineBasedFrameDecoder(8 * 1024));
+							         }
+						         }))
+				.listen(port)
+				.codec(StandardCodecs.STRING_CODEC)
+				.consume(serverHandler)
+				.get();
+
+		server.start().await();
+
+		client.open().await().sendAndForget("Hello World!").sendAndForget("Hello World!");
+
+		assertTrue("Latch was counted down", latch.await(5, TimeUnit.SECONDS));
+
+		client.close().await();
+		server.shutdown().await();
+	}
+
+	@Test
+	public void exposesNettyByteBuf() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+		final CountDownLatch latch = new CountDownLatch(msgs);
+
+		TcpServer<ByteBuf, ByteBuf> server = new TcpServerSpec<ByteBuf, ByteBuf>(NettyTcpServer.class)
+				.env(env)
+				.listen(port)
+				.dispatcher(new SynchronousDispatcher())
+				.consume(new Consumer<NetChannel<ByteBuf, ByteBuf>>() {
+					@Override
+					public void accept(NetChannel<ByteBuf, ByteBuf> ch) {
+						ch.consume(new Consumer<ByteBuf>() {
+							@Override
+							public void accept(ByteBuf byteBuf) {
+								byteBuf.forEachByte(new ByteBufProcessor() {
+									@Override
+									public boolean process(byte value) throws Exception {
+										if (value == '\n') {
+											latch.countDown();
+										}
+										return true;
+									}
+								});
+								byteBuf.release();
+							}
+						});
+					}
+				})
+				.get();
+
+		log.info("Starting raw server on tcp://localhost:{}", port);
+		server.start().await();
+
+		for (int i = 0; i < threads; i++) {
+			threadPool.submit(new DataWriter(port));
+		}
+
+		try {
+			assertTrue("Latch was counted down", latch.await(10, TimeUnit.SECONDS));
+		} finally {
+			server.shutdown().await();
+		}
+
+	}
+
+	@Test
+	public void exposesHttpServer() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+
+		final TcpServer<HttpRequest, HttpResponse> server
+				= new TcpServerSpec<HttpRequest, HttpResponse>(NettyTcpServer.class)
+				.env(env)
+				.listen(port)
+				.options(new NettyServerSocketOptions()
+						         .pipelineConfigurer(new Consumer<ChannelPipeline>() {
+							         @Override
+							         public void accept(ChannelPipeline pipeline) {
+								         pipeline.addLast(new HttpRequestDecoder());
+								         pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
+								         pipeline.addLast(new HttpResponseEncoder());
+							         }
+						         }))
+				.consume(new Consumer<NetChannel<HttpRequest, HttpResponse>>() {
+					@Override
+					public void accept(final NetChannel<HttpRequest, HttpResponse> ch) {
+						ch.in().consume(new Consumer<HttpRequest>() {
+							@Override
+							public void accept(HttpRequest req) {
+								ByteBuf buf = Unpooled.copiedBuffer("Hello World!".getBytes());
+								int len = buf.readableBytes();
+								DefaultFullHttpResponse resp = new DefaultFullHttpResponse(
+										HttpVersion.HTTP_1_1,
+										HttpResponseStatus.OK,
+										buf
+								);
+								resp.headers().set(HttpHeaders.Names.CONTENT_LENGTH, len);
+								resp.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
+								resp.headers().set(HttpHeaders.Names.CONNECTION, "Keep-Alive");
+
+								ch.send(resp);
+
+								if (req.getMethod() == HttpMethod.GET && "/test".equals(req.getUri())) {
+									latch.countDown();
+								}
+							}
+						});
+					}
+				})
+				.get();
+
+		log.info("Starting HTTP server on http://localhost:{}/", port);
+		server.start().await();
+
+		for (int i = 0; i < threads; i++) {
+			threadPool.submit(new HttpRequestWriter(port));
+		}
+
+		assertTrue("Latch was counted down", latch.await(15, TimeUnit.SECONDS));
+		end.set(System.currentTimeMillis());
+
+		double elapsed = (end.get() - start.get());
+		System.out.println("HTTP elapsed: " + (int) elapsed + "ms");
+		System.out.println("HTTP throughput: " + (int) ((msgs * threads) / (elapsed / 1000)) + "/sec");
+
+		server.shutdown().await();
+	}
+
+	@Test(timeout = 60000)
+	public void exposesZeroMQServer() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+		final CountDownLatch latch = new CountDownLatch(2);
+		ZContext zmq = new ZContext();
+
+		NetServer<Buffer, Buffer> server = new TcpServerSpec<Buffer, Buffer>(ZeroMQTcpServer.class)
+				.env(env)
+				.listen(port)
+				.consume(ch -> {
+					ch.consume(buff -> {
+						if (buff.remaining() == 128) {
+							latch.countDown();
+						} else {
+							log.info("data: {}", buff.asString());
+						}
+						ch.sendAndForget(Buffer.wrap("Goodbye World!"));
+					});
+				})
+				.get();
+
+		assertTrue("Server was started", server.start().await(5, TimeUnit.SECONDS));
+
+		ZeroMQWriter zmqw = new ZeroMQWriter(zmq, port, latch);
+		threadPool.submit(zmqw);
+
+		assertTrue("reply was received", latch.await(5, TimeUnit.SECONDS));
+		assertTrue("Server was stopped", server.shutdown().await(5, TimeUnit.SECONDS));
+
+		zmq.destroy();
+	}
+
+	public static class Pojo {
+		private String name;
+
+		private Pojo() {
+		}
+
+		private Pojo(String name) {
+			this.name = name;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+
+		@Override
+		public String toString() {
+			return "Pojo{" +
+					"name='" + name + '\'' +
+					'}';
+		}
+	}
+
+	private class LengthFieldMessageWriter implements Runnable {
+		private final Random rand = new Random();
+		private final int port;
+		private final int length;
+
+		private LengthFieldMessageWriter(int port) {
+			this.port = port;
+			this.length = rand.nextInt(256);
+		}
+
+		@Override
+		public void run() {
+			try {
+				java.nio.channels.SocketChannel ch = java.nio.channels.SocketChannel.open(new InetSocketAddress(port));
+
+				System.out.println("writing " + msgs + " messages of " + length + " byte length...");
+
+				int num = 1;
+				start.set(System.currentTimeMillis());
+				for (int j = 0; j < msgs; j++) {
+					ByteBuffer buff = ByteBuffer.allocate(length + 4);
+					buff.putInt(length);
+					buff.putInt(num++);
+					buff.position(0);
+					buff.limit(length + 4);
+
+					ch.write(buff);
+
+					count.incrementAndGet();
+				}
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	private class FramedLengthFieldMessageWriter implements Runnable {
+		private final short length = 128;
+		private final int port;
+
+		private FramedLengthFieldMessageWriter(int port) {
+			this.port = port;
+		}
+
+		@Override
+		public void run() {
+			try {
+				java.nio.channels.SocketChannel ch = java.nio.channels.SocketChannel.open(new InetSocketAddress(port));
+
+				System.out.println("writing " + msgs + " messages of " + length + " byte length...");
+
+				start.set(System.currentTimeMillis());
+				for (int j = 0; j < msgs; j++) {
+					ByteBuffer buff = ByteBuffer.allocate(length + 4);
+					buff.putShort((short) 0);
+					buff.putShort(length);
+					for (int i = 4; i < length; i++) {
+						buff.put((byte) 1);
+					}
+					buff.flip();
+					buff.limit(length + 4);
+
+					ch.write(buff);
+
+					count.incrementAndGet();
+				}
+				ch.close();
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	private class HttpRequestWriter implements Runnable {
+		private final int port;
+
+		private HttpRequestWriter(int port) {
+			this.port = port;
+		}
+
+		@Override
+		public void run() {
+			try {
+				java.nio.channels.SocketChannel ch = java.nio.channels.SocketChannel.open(new InetSocketAddress(port));
+				start.set(System.currentTimeMillis());
+				for (int i = 0; i < msgs; i++) {
+					ch.write(Buffer.wrap("GET /test HTTP/1.1\r\nConnection: Close\r\n\r\n").byteBuffer());
+					ByteBuffer buff = ByteBuffer.allocate(4 * 1024);
+					ch.read(buff);
+				}
+				ch.close();
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	private class DataWriter implements Runnable {
+		private final int port;
+
+		private DataWriter(int port) {
+			this.port = port;
+		}
+
+		@Override
+		public void run() {
+			try {
+				java.nio.channels.SocketChannel ch = java.nio.channels.SocketChannel.open(new InetSocketAddress(port));
+				start.set(System.currentTimeMillis());
+				for (int i = 0; i < msgs; i++) {
+					ch.write(Buffer.wrap("Hello World!\n").byteBuffer());
+				}
+				ch.close();
+			} catch (IOException e) {
+			}
+		}
+	}
+
+	private class ZeroMQWriter implements Runnable {
+		private final Random random = new Random();
+		private final ZContext    zmq;
+		private final int            port;
+		private final CountDownLatch latch;
+
+		private ZeroMQWriter(ZContext zmq, int port, CountDownLatch latch) {
+			this.zmq = zmq;
+			this.port = port;
+			this.latch = latch;
+		}
+
+		@Override
+		public void run() {
+			String id = UUIDUtils.random().toString();
+			ZMQ.Socket socket = zmq.createSocket(ZMQ.DEALER);
+			socket.setIdentity(id.getBytes());
+			socket.connect("tcp://127.0.0.1:" + port);
+
+			byte[] data = new byte[128];
+			random.nextBytes(data);
+
+			socket.send(data);
+
+			ZMsg reply = ZMsg.recvMsg(socket);
+			log.info("reply: {}", reply);
+			latch.countDown();
+
+			//zmq.destroySocket(socket);
+		}
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/ZeroMQClientServerTests.java b/reactor-net/src/test/java/reactor/net/tcp/ZeroMQClientServerTests.java
new file mode 100644
index 0000000..c6ab584
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/ZeroMQClientServerTests.java
@@ -0,0 +1,133 @@
+package reactor.net.tcp;
+
+import com.esotericsoftware.kryo.Kryo;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.core.Environment;
+import reactor.io.Buffer;
+import reactor.io.encoding.json.JacksonJsonCodec;
+import reactor.io.encoding.kryo.KryoCodec;
+import reactor.net.AbstractNetClientServerTest;
+import reactor.net.zmq.tcp.ZeroMQ;
+import reactor.net.zmq.tcp.ZeroMQTcpClient;
+import reactor.net.zmq.tcp.ZeroMQTcpServer;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Jon Brisbin
+ */
+ at Ignore
+public class ZeroMQClientServerTests extends AbstractNetClientServerTest {
+
+	static Kryo                  KRYO;
+	static KryoCodec<Data, Data> KRYO_CODEC;
+	static ZeroMQ<Data>          ZMQ;
+
+	CountDownLatch latch;
+
+	@BeforeClass
+	public static void classSetup() {
+		KRYO = new Kryo();
+		KRYO_CODEC = new KryoCodec<>(KRYO, false);
+		ZMQ = new ZeroMQ<Data>(new Environment()).codec(KRYO_CODEC);
+	}
+
+	@AfterClass
+	public static void classCleanup() {
+		ZMQ.shutdown();
+	}
+
+	@Override
+	public void setup() {
+		super.setup();
+		latch = new CountDownLatch(1);
+	}
+
+	@Test(timeout = 60000)
+	public void clientSendsDataToServerUsingKryo() throws InterruptedException {
+		assertTcpClientServerExchangedData(ZeroMQTcpServer.class,
+		                                   ZeroMQTcpClient.class,
+		                                   KRYO_CODEC,
+		                                   data,
+		                                   d -> d.equals(data));
+	}
+
+	@Test(timeout = 60000)
+	public void clientSendsDataToServerUsingJson() throws InterruptedException {
+		assertTcpClientServerExchangedData(ZeroMQTcpServer.class,
+		                                   ZeroMQTcpClient.class,
+		                                   new JacksonJsonCodec<>(),
+		                                   data,
+		                                   d -> d.equals(data));
+	}
+
+	@Test(timeout = 60000)
+	public void clientSendsDataToServerUsingBuffers() throws InterruptedException {
+		assertTcpClientServerExchangedData(ZeroMQTcpServer.class,
+		                                   ZeroMQTcpClient.class,
+		                                   Buffer.wrap("Hello World!"));
+	}
+
+	@Test(timeout = 60000)
+	public void zmqRequestReply() throws InterruptedException {
+		ZMQ.reply("tcp://*:" + getPort())
+		   .consume(ch -> ch.consume(ch::send));
+
+		ZMQ.request("tcp://127.0.0.1:" + getPort())
+		   .consume(ch -> {
+			   ch.sendAndReceive(data)
+			     .consume(data -> latch.countDown());
+		   });
+
+		assertTrue("REQ/REP socket exchanged data", latch.await(60, TimeUnit.SECONDS));
+	}
+
+	@Test(timeout = 60000)
+	public void zmqPushPull() throws InterruptedException {
+		ZMQ.pull("tcp://*:" + getPort())
+		   .consume(ch -> latch.countDown());
+
+		ZMQ.push("tcp://127.0.0.1:" + getPort())
+		   .consume(ch -> ch.send(data));
+
+		assertTrue("PULL socket received data", latch.await(1, TimeUnit.SECONDS));
+	}
+
+	@Test(timeout = 60000)
+	public void zmqRouterDealer() throws InterruptedException {
+		ZMQ.router("tcp://*:" + getPort())
+		   .consume(ch -> latch.countDown());
+
+		ZMQ.dealer("tcp://127.0.0.1:" + getPort())
+		   .consume(ch -> ch.send(data));
+
+		assertTrue("ROUTER socket received data", latch.await(1, TimeUnit.SECONDS));
+	}
+
+	@Test(timeout = 60000)
+	public void zmqInprocRouterDealer() throws InterruptedException {
+		ZMQ.router("inproc://queue" + getPort())
+		   .consume(ch -> {
+			   ch.consume(data -> {
+				   latch.countDown();
+			   });
+		   });
+
+		// we have to sleep a couple cycles to let ZeroMQ get set up on inproc
+		Thread.sleep(500);
+
+		ZMQ.dealer("inproc://queue" + getPort())
+		   .consume(ch -> {
+			   ch.sendAndForget(data);
+		   });
+
+		assertTrue("ROUTER socket received inproc data", latch.await(1, TimeUnit.SECONDS));
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/syslog/SyslogTcpServerTests.java b/reactor-net/src/test/java/reactor/net/tcp/syslog/SyslogTcpServerTests.java
new file mode 100644
index 0000000..a6383fc
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/syslog/SyslogTcpServerTests.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.syslog;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.DelimiterBasedFrameDecoder;
+import io.netty.handler.codec.Delimiters;
+import io.netty.handler.codec.MessageToMessageDecoder;
+import io.netty.handler.codec.string.StringDecoder;
+import org.apache.hadoop.conf.Configuration;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.core.Environment;
+import reactor.function.Consumer;
+import reactor.function.Function;
+import reactor.io.Buffer;
+import reactor.net.NetChannel;
+import reactor.net.encoding.syslog.SyslogCodec;
+import reactor.net.encoding.syslog.SyslogMessage;
+import reactor.net.netty.tcp.NettyTcpServer;
+import reactor.net.tcp.TcpServer;
+import reactor.net.tcp.spec.TcpServerSpec;
+import reactor.net.tcp.syslog.hdfs.HdfsConsumer;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * @author Jon Brisbin
+ * @author Stephane Maldini
+ */
+public class SyslogTcpServerTests {
+
+	static final byte[] SYSLOG_MESSAGE_DATA = ("<34>Oct 11 22:14:15 mymachine su: 'su root' failed for lonvick on " +
+			"/dev/pts/8\n")
+			.getBytes();
+
+	final int msgs    = 2000000;
+	final int threads = 4;
+
+	Environment    env;
+	CountDownLatch latch;
+	AtomicLong count = new AtomicLong();
+	AtomicLong start = new AtomicLong();
+	AtomicLong end   = new AtomicLong();
+
+	@Before
+	public void loadEnv() {
+		env = new Environment();
+		latch = new CountDownLatch(msgs * threads);
+	}
+
+	@Test
+	@Ignore
+	public void testSyslogServer() throws InterruptedException, IOException {
+		EventLoopGroup bossGroup = new NioEventLoopGroup(2);
+		EventLoopGroup workerGroup = new NioEventLoopGroup(4);
+
+		Configuration conf = new Configuration();
+		conf.addResource("/usr/local/Cellar/hadoop/1.1.2/libexec/conf/core-site.xml");
+		final HdfsConsumer hdfs = new HdfsConsumer(conf, "loadtests", "syslog");
+
+		ServerBootstrap b = new ServerBootstrap();
+		b.group(bossGroup, workerGroup)
+		 .localAddress(3000)
+		 .channel(NioServerSocketChannel.class)
+		 .childHandler(new ChannelInitializer<SocketChannel>() {
+			 @Override
+			 public void initChannel(SocketChannel ch) throws Exception {
+				 ChannelPipeline pipeline = ch.pipeline();
+				 pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
+				 pipeline.addLast("decoder", new StringDecoder());
+				 pipeline.addLast("syslogDecoder", new MessageToMessageDecoder<String>() {
+					 Function<Buffer, SyslogMessage> decoder = new SyslogCodec().decoder(null);
+
+					 @Override
+					 public void decode(ChannelHandlerContext ctx, String msg, List<Object> messages) throws Exception {
+						 messages.add(decoder.apply(Buffer.wrap(msg + "\n")));
+					 }
+				 });
+				 pipeline.addLast("handler", new ChannelInboundHandlerAdapter() {
+
+					 @Override
+					 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+						 latch.countDown();
+						 hdfs.accept((SyslogMessage)msg);
+					 }
+				 });
+			 }
+		 });
+
+		// Bind and start to accept incoming connections.
+		ChannelFuture channelFuture = b.bind().awaitUninterruptibly();
+
+		for(int i = 0; i < threads; i++) {
+			new SyslogMessageWriter(3000).start();
+		}
+
+		latch.await(60, TimeUnit.SECONDS);
+		end.set(System.currentTimeMillis());
+
+		assertThat("latch was counted down", latch.getCount(), is(0L));
+
+		double elapsed = (end.get() - start.get()) * 1.0;
+		System.out.println("elapsed: " + (int)elapsed + "ms");
+		System.out.println("throughput: " + (int)((msgs * threads) / (elapsed / 1000)) + "/sec");
+
+		channelFuture.channel().close().awaitUninterruptibly();
+	}
+
+	@Test
+	@Ignore
+	public void testTcpSyslogServer() throws InterruptedException, IOException {
+		//final FileChannelConsumer<SyslogMessage> fcc = new FileChannelConsumer<SyslogMessage>(".", "syslog", -1, -1);
+		Configuration conf = new Configuration();
+		conf.addResource("/usr/local/Cellar/hadoop/1.1.2/libexec/conf/core-site.xml");
+		final HdfsConsumer hdfs = new HdfsConsumer(conf, "loadtests", "syslog");
+
+		TcpServer<SyslogMessage, Void> server = new TcpServerSpec<SyslogMessage, Void>(NettyTcpServer.class)
+				.env(env)
+						//.using(SynchronousDispatcher.INSTANCE)
+						//.dispatcher(Environment.EVENT_LOOP)
+				.dispatcher(Environment.RING_BUFFER)
+				.codec(new SyslogCodec())
+				.consume(new Consumer<NetChannel<SyslogMessage, Void>>() {
+					@Override
+					public void accept(NetChannel<SyslogMessage, Void> conn) {
+						conn
+								.consume(new Consumer<SyslogMessage>() {
+									@Override
+									public void accept(SyslogMessage msg) {
+										count.incrementAndGet();
+									}
+								})
+								.consume(hdfs);
+					}
+				})
+				.get();
+
+		server.start().await();
+
+		for(int i = 0; i < threads; i++) {
+			new SyslogMessageWriter(3000).start();
+		}
+
+		while(count.get() < (msgs * threads)) {
+			end.set(System.currentTimeMillis());
+			Thread.sleep(100);
+		}
+
+		double elapsed = (end.get() - start.get()) * 1.0;
+		System.out.println("elapsed: " + (int)elapsed + "ms");
+		System.out.println("throughput: " + (int)((msgs * threads) / (elapsed / 1000)) + "/sec");
+
+		server.shutdown();
+	}
+
+	@Test
+	@Ignore
+	public void testExternalServer() throws InterruptedException {
+		CountDownLatch latch = new CountDownLatch(1);
+
+		long start = System.currentTimeMillis();
+		SyslogMessageWriter[] writers = new SyslogMessageWriter[threads];
+		for(int i = 0; i < threads; i++) {
+			writers[i] = new SyslogMessageWriter(5140);
+			writers[i].start();
+		}
+
+		latch.await(10, TimeUnit.SECONDS);
+		// Calculate exact time, which will be slightly over timeout
+		long end = System.currentTimeMillis();
+		double elapsed = (end - start) * 1.0;
+
+		int totalMsgs = 0;
+		for(int i = 0; i < threads; i++) {
+			totalMsgs += writers[i].count.intValue();
+		}
+
+		System.out.println("throughput: " + (int)((totalMsgs) / (elapsed / 1000)) + "/sec");
+	}
+
+	private class SyslogMessageWriter extends Thread {
+		AtomicLong count = new AtomicLong();
+		private final int port;
+
+		private SyslogMessageWriter(int port) {
+			this.port = port;
+		}
+
+		@Override
+		public void run() {
+			try {
+				java.nio.channels.SocketChannel ch = java.nio.channels.SocketChannel.open(new InetSocketAddress(port));
+
+				start.set(System.currentTimeMillis());
+				for(int i = 0; i < msgs; i++) {
+					ch.write(ByteBuffer.wrap(SYSLOG_MESSAGE_DATA));
+					count.incrementAndGet();
+				}
+			} catch(IOException e) {
+			}
+		}
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/syslog/hdfs/HdfsConsumer.java b/reactor-net/src/test/java/reactor/net/tcp/syslog/hdfs/HdfsConsumer.java
new file mode 100644
index 0000000..5f2e6e7
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/syslog/hdfs/HdfsConsumer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.syslog.hdfs;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import reactor.function.Consumer;
+import reactor.io.Buffer;
+import reactor.net.encoding.syslog.SyslogMessage;
+
+import java.io.IOException;
+
+/**
+ * @author Jon Brisbin
+ */
+public class HdfsConsumer implements Consumer<SyslogMessage> {
+
+	private final FSDataOutputStream out;
+
+	public HdfsConsumer(Configuration conf, String dir, String name) throws IOException {
+		Path path = new Path(dir, name);
+		FileSystem fs = path.getFileSystem(conf);
+		out = fs.create(path, true);
+	}
+
+	@Override
+	public void accept(SyslogMessage msg) {
+		try {
+			byte[] bytes = msg.toString().getBytes();
+			int len = bytes.length;
+			for (int i = 0; i < len; i += Buffer.SMALL_BUFFER_SIZE) {
+				int size = Math.min(len - i, Buffer.SMALL_BUFFER_SIZE);
+				out.write(bytes, i, size);
+			}
+		} catch (IOException e) {
+			throw new IllegalStateException(e);
+		}
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/syslog/test/Severity.java b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/Severity.java
new file mode 100644
index 0000000..d51259c
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/Severity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.syslog.test;
+
+/**
+ * The possible severities of a syslog message
+ *
+ * @author Andy Wilkinson
+ *
+ */
+public enum Severity {
+	EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG;
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogCodec.java b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogCodec.java
new file mode 100644
index 0000000..52ab147
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogCodec.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.syslog.test;
+
+import reactor.function.Function;
+import reactor.io.Buffer;
+
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+
+/**
+ * @author Jon Brisbin
+ */
+public class SyslogCodec  {
+
+	private final Charset utf8 = Charset.forName("UTF-8");
+
+	//@Override
+	public Function<Buffer, SyslogMessage> decoder() {
+		return new SyslogMessageDecoder();
+	}
+
+	//@Override
+	public Function<Void, Buffer> encoder() {
+		return new Function<Void, Buffer>() {
+			@Override
+			public Buffer apply(Void aVoid) {
+				return null;
+			}
+		};
+	}
+
+	private class SyslogMessageDecoder implements Function<Buffer, SyslogMessage> {
+		private final CharsetDecoder decoder = utf8.newDecoder();
+
+		@Override
+		public SyslogMessage apply(Buffer buffer) {
+			try {
+				String s = decoder.decode(buffer.byteBuffer()).toString();
+				return SyslogMessageParser.parse(s);
+			} catch (CharacterCodingException e) {
+				throw new IllegalArgumentException(e.getMessage(), e);
+			}
+		}
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogMessage.java b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogMessage.java
new file mode 100644
index 0000000..033253d
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogMessage.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.syslog.test;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class SyslogMessage {
+
+
+	private static final Pattern TAG_AND_CONTENT_PATTERN = Pattern.compile("([A-Za-z0-9]+)(.*)");
+
+	private final int facility;
+
+	private final Severity severity;
+
+	private final Timestamp timestamp;
+
+	private final String host;
+
+	private final String message;
+
+	SyslogMessage(int facility, Severity severity, Timestamp timestamp, String host, String message) {
+		this.facility = facility;
+		this.severity = severity;
+		this.timestamp = timestamp;
+		this.host = host;
+		this.message = message;
+	}
+
+	public int getFacility() {
+		return facility;
+	}
+
+	public Severity getSeverity() {
+		return severity;
+	}
+
+	public Timestamp getTimestamp() {
+		return timestamp;
+	}
+
+	public String getHost() {
+		return host;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public String getTag() {
+		Matcher matcher = TAG_AND_CONTENT_PATTERN.matcher(message);
+		if (matcher.matches()) {
+			return matcher.group(1);
+		} else {
+			return null;
+		}
+	}
+
+	public String getContent() {
+		Matcher matcher = TAG_AND_CONTENT_PATTERN.matcher(message);
+		if (matcher.matches()) {
+			return matcher.group(2);
+		} else {
+			return null;
+		}
+	}
+
+	@Override
+	public String toString() {
+		return "SyslogMessage [facility=" + facility + ", severity=" + severity + ", timestamp=" + timestamp + ", host=" + host + ", message=" + message + "]";
+	}
+}
\ No newline at end of file
diff --git a/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogMessageParser.java b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogMessageParser.java
new file mode 100644
index 0000000..66aad9f
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/SyslogMessageParser.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.syslog.test;
+
+import java.util.Calendar;
+
+class SyslogMessageParser {
+
+	private static final int MAXIMUM_SEVERITY = 7;
+
+	private static final int MAXIMUM_FACILITY = 23;
+
+	private static final int MINIMUM_PRI = 0;
+
+	private static final int MAXIMUM_PRI = (MAXIMUM_FACILITY * 8) + MAXIMUM_SEVERITY;
+
+	private static final int DEFAULT_PRI = 13;
+
+	public static SyslogMessage parse(String raw) {
+		if (null == raw || raw.isEmpty()) {
+			return null;
+		}
+
+		int pri = DEFAULT_PRI;
+		Timestamp timestamp = null;
+		String host = null;
+
+		int index = 0;
+
+		if (raw.indexOf('<') == 0) {
+			int endPri = raw.indexOf('>');
+			if (endPri >= 2 && endPri <= 4) {
+				int candidatePri = Integer.parseInt(raw.substring(1, endPri));
+				if (candidatePri >= MINIMUM_PRI && candidatePri <= MAXIMUM_PRI) {
+					pri = candidatePri;
+					index = endPri + 1;
+					timestamp = new Timestamp(raw.substring(index, index + 15));
+					index += 16;
+					int endHostnameIndex = raw.indexOf(' ', index);
+					host = raw.substring(index, endHostnameIndex);
+					index = endHostnameIndex + 1;
+				}
+			}
+		}
+
+		if (host == null) {
+			// TODO Need a way to populate host with client's IP address
+		}
+
+		int facility = pri / 8;
+		Severity severity = Severity.values()[pri % 8];
+
+		if (timestamp == null) {
+			timestamp = new Timestamp(Calendar.getInstance());
+		}
+
+		return new SyslogMessage(facility, severity, timestamp, host, raw.substring(index));
+	}
+
+}
diff --git a/reactor-net/src/test/java/reactor/net/tcp/syslog/test/Timestamp.java b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/Timestamp.java
new file mode 100644
index 0000000..2b3a197
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/tcp/syslog/test/Timestamp.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package reactor.net.tcp.syslog.test;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+
+public final class Timestamp {
+
+	private static final List<String> MONTHS = Arrays.asList("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
+	private final int month;
+	private final int day;
+	private final int hours;
+	private final int minutes;
+	private final int seconds;
+
+	Timestamp(Calendar instance) {
+		month = instance.get(Calendar.MONTH);
+		day = instance.get(Calendar.DAY_OF_MONTH);
+		hours = instance.get(Calendar.HOUR_OF_DAY);
+		minutes = instance.get(Calendar.MINUTE);
+		seconds = instance.get(Calendar.SECOND);
+	}
+
+	Timestamp(String toParse) {
+		String monthString = toParse.substring(0, 3);
+		month = MONTHS.indexOf(monthString);
+		day = Integer.parseInt(toParse.substring(4, 6));
+		hours = Integer.parseInt(toParse.substring(7, 9));
+		minutes = Integer.parseInt(toParse.substring(10, 12));
+		seconds = Integer.parseInt(toParse.substring(13));
+	}
+
+	public int getMonth() {
+		return month;
+	}
+
+	public int getDay() {
+		return day;
+	}
+
+	public int getHours() {
+		return hours;
+	}
+
+	public int getMinutes() {
+		return minutes;
+	}
+
+	public int getSeconds() {
+		return seconds;
+	}
+
+	public String toString() {
+		StringBuilder builder = new StringBuilder(MONTHS.get(month));
+		builder.append(" ");
+		builder.append(day);
+		builder.append(" ");
+		builder.append(hours);
+		builder.append(":");
+		builder.append(minutes);
+		builder.append(":");
+		builder.append(seconds);
+		return builder.toString();
+	}
+}
diff --git a/reactor-net/src/test/java/reactor/net/udp/UdpServerTests.java b/reactor-net/src/test/java/reactor/net/udp/UdpServerTests.java
new file mode 100644
index 0000000..dec2871
--- /dev/null
+++ b/reactor-net/src/test/java/reactor/net/udp/UdpServerTests.java
@@ -0,0 +1,174 @@
+package reactor.net.udp;
+
+import io.netty.util.NetUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.Environment;
+import reactor.function.Consumer;
+import reactor.io.encoding.StandardCodecs;
+import reactor.net.config.ServerSocketOptions;
+import reactor.net.netty.udp.NettyDatagramServer;
+import reactor.net.tcp.support.SocketUtils;
+import reactor.net.udp.spec.DatagramServerSpec;
+
+import java.io.IOException;
+import java.net.*;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.concurrent.*;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * @author Jon Brisbin
+ */
+public class UdpServerTests {
+
+	final Logger log = LoggerFactory.getLogger(getClass());
+
+	Environment     env;
+	ExecutorService threadPool;
+
+	@Before
+	public void setup() {
+		env = new Environment();
+		threadPool = Executors.newCachedThreadPool();
+	}
+
+	@After
+	public void cleanup() throws InterruptedException {
+		threadPool.shutdown();
+		threadPool.awaitTermination(5, TimeUnit.SECONDS);
+	}
+
+	@Test
+	@Ignore
+	public void supportsReceivingDatagrams() throws InterruptedException {
+		final int port = SocketUtils.findAvailableTcpPort();
+		final CountDownLatch latch = new CountDownLatch(4);
+
+		final DatagramServer<byte[], byte[]> server = new DatagramServerSpec<byte[], byte[]>(NettyDatagramServer.class)
+				.env(env)
+				.listen(port)
+				.codec(StandardCodecs.BYTE_ARRAY_CODEC)
+				.consumeInput(new Consumer<byte[]>() {
+					@Override
+					public void accept(byte[] bytes) {
+						if (bytes.length == 1024) {
+							latch.countDown();
+						}
+					}
+				})
+				.get();
+
+		server.start(new Runnable() {
+			@Override
+			public void run() {
+				try {
+					DatagramChannel udp = DatagramChannel.open();
+					udp.configureBlocking(true);
+					udp.connect(new InetSocketAddress(port));
+
+					byte[] data = new byte[1024];
+					new Random().nextBytes(data);
+					for (int i = 0; i < 4; i++) {
+						udp.write(ByteBuffer.wrap(data));
+					}
+
+					udp.close();
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			}
+		});
+
+		assertThat("latch was counted down", latch.await(5, TimeUnit.SECONDS));
+	}
+
+	@Test
+	@Ignore
+	public void supportsUdpMulticast() throws InterruptedException,
+	                                          UnknownHostException,
+	                                          SocketException,
+	                                          TimeoutException,
+	                                          ExecutionException {
+		final int port = SocketUtils.findAvailableTcpPort();
+		final CountDownLatch latch = new CountDownLatch(Environment.PROCESSORS ^ 2);
+
+		final InetAddress multicastGroup = InetAddress.getByName("230.0.0.1");
+		final NetworkInterface multicastIface = findMulticastInterface();
+		final DatagramServer[] servers = new DatagramServer[Environment.PROCESSORS];
+
+		for (int i = 0; i < Environment.PROCESSORS; i++) {
+			servers[i] = new DatagramServerSpec<byte[], byte[]>(NettyDatagramServer.class)
+					.env(env)
+					.dispatcher(Environment.THREAD_POOL)
+					.listen(port)
+					.multicastInterface(multicastIface)
+					.options(new ServerSocketOptions()
+							         .reuseAddr(true))
+					.codec(StandardCodecs.BYTE_ARRAY_CODEC)
+					.consumeInput(new Consumer<byte[]>() {
+						int count = 0;
+
+						@SuppressWarnings("unchecked")
+						@Override
+						public void accept(byte[] bytes) {
+							//log.info("{} got {} bytes", ++count, bytes.length);
+							if (bytes.length == 1024) {
+								latch.countDown();
+							}
+						}
+					})
+					.get();
+
+			servers[i].start().await();
+			servers[i].join(multicastGroup).await();
+		}
+
+		for (int i = 0; i < Environment.PROCESSORS; i++) {
+			threadPool.submit(new Runnable() {
+				@Override
+				public void run() {
+					try {
+						MulticastSocket multicast = new MulticastSocket(port);
+						multicast.joinGroup(multicastGroup);
+
+						byte[] data = new byte[1024];
+						new Random().nextBytes(data);
+
+						multicast.send(new DatagramPacket(data, data.length, multicastGroup, port));
+
+						multicast.close();
+					} catch (Exception e) {
+						throw new IllegalStateException(e);
+					}
+				}
+			}).get(5, TimeUnit.SECONDS);
+		}
+
+		assertThat("latch was counted down", latch.await(5, TimeUnit.SECONDS));
+
+		for (DatagramServer s : servers) {
+			s.shutdown().await();
+		}
+	}
+
+	private NetworkInterface findMulticastInterface() throws SocketException {
+		Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
+		while (ifaces.hasMoreElements()) {
+			NetworkInterface iface = ifaces.nextElement();
+			if (!iface.isLoopback() && iface.supportsMulticast()) {
+				return iface;
+			}
+		}
+		return NetUtil.LOOPBACK_IF;
+	}
+
+}
diff --git a/reactor-net/src/test/resources/META-INF/reactor/default.properties b/reactor-net/src/test/resources/META-INF/reactor/default.properties
new file mode 100644
index 0000000..bab2599
--- /dev/null
+++ b/reactor-net/src/test/resources/META-INF/reactor/default.properties
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2011-2013 GoPivotal, Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##
+# Dispatcher configuration
+#
+# Each dispatcher must be configured with a type:
+#
+# reactor.dispatchers.<name>.type = <type>
+#
+# Legal values for <type> are eventLoop, ringBuffer, synchronous, and threadPoolExecutor.
+
+# Depending on the type, further configuration is be possible:
+#
+# reactor.dispatchers.<name>.size:    eventLoop and threadPoolExecutor Dispatchers
+# reactor.dispatchers.<name>.backlog: eventLoop, ringBuffer, and threadPoolExecutor Dispatchers
+#
+# A size less than 1 may be specified to indicate that the size should be the same as the number
+# of CPUs.
+
+# A thread pool executor dispatcher, named threadPoolExecutor
+reactor.dispatchers.threadPoolExecutor.type = threadPoolExecutor
+reactor.dispatchers.threadPoolExecutor.size = 16
+# Backlog is how many Task objects to warm up internally
+reactor.dispatchers.threadPoolExecutor.backlog = 1024
+
+# An event loop dispatcher, named eventLoop
+reactor.dispatchers.eventLoop.type = eventLoop
+reactor.dispatchers.eventLoop.size = 0
+reactor.dispatchers.eventLoop.backlog = 256
+
+# A ring buffer dispatcher, named ringBuffer
+reactor.dispatchers.ringBuffer.type = ringBuffer
+reactor.dispatchers.ringBuffer.backlog = 1024
+
+# The dispatcher named ringBuffer should be the default dispatcher
+reactor.dispatchers.default = ringBuffer
+
+reactor.tcp.selectThreadCount = 2
+reactor.tcp.ioThreadCount = 4
+reactor.tcp.connectionReactorBacklog = 128
\ No newline at end of file
diff --git a/reactor-net/src/test/resources/client.cer b/reactor-net/src/test/resources/client.cer
new file mode 100644
index 0000000..83a548c
Binary files /dev/null and b/reactor-net/src/test/resources/client.cer differ
diff --git a/reactor-net/src/test/resources/client.jks b/reactor-net/src/test/resources/client.jks
new file mode 100644
index 0000000..e177f8c
Binary files /dev/null and b/reactor-net/src/test/resources/client.jks differ
diff --git a/reactor-net/src/test/resources/server.cer b/reactor-net/src/test/resources/server.cer
new file mode 100644
index 0000000..c8622d3
Binary files /dev/null and b/reactor-net/src/test/resources/server.cer differ
diff --git a/reactor-net/src/test/resources/server.jks b/reactor-net/src/test/resources/server.jks
new file mode 100644
index 0000000..bfd2813
Binary files /dev/null and b/reactor-net/src/test/resources/server.jks differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..8046465
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,7 @@
+rootProject.name = 'reactor'
+
+include 'reactor-core',
+        'reactor-groovy-extensions',
+		'reactor-groovy',
+		'reactor-logback',
+		'reactor-net'
diff --git a/src/api/overview.html b/src/api/overview.html
new file mode 100644
index 0000000..c2a4cd5
--- /dev/null
+++ b/src/api/overview.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title></title>
+</head>
+<body>
+This document is the API specification for the Reactor FastData library.
+<hr/>
+<div id="overviewBody">
+    <p>
+        Reactor is a succinct and powerful foundational library for building reactive, FastData applications on the JVM.
+        More detailed documentation is available on the <a href="https://github.com/reactor/reactor/wiki"
+                                                           target="_blank">GitHub Wiki pages for Project Reactor</a>.
+    </p>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/api/stylesheet.css b/src/api/stylesheet.css
new file mode 100644
index 0000000..3c4930f
--- /dev/null
+++ b/src/api/stylesheet.css
@@ -0,0 +1,640 @@
+/* Javadoc style sheet */
+
+/*
+Overall document style
+*/
+body {
+    background-color: #ffffff;
+    color: #353833;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 76%;
+    margin: 0;
+}
+
+a:link, a:visited {
+    text-decoration: none;
+    color: #4c6b87;
+}
+
+a:hover, a:focus {
+    text-decoration: none;
+    color: #bb7a2a;
+}
+
+a:active {
+    text-decoration: none;
+    color: #4c6b87;
+}
+
+a[name] {
+    color: #353833;
+}
+
+a[name]:hover {
+    text-decoration: none;
+    color: #353833;
+}
+
+pre {
+    font-size: 1.3em;
+}
+
+h1 {
+    font-size: 1.8em;
+}
+
+h2 {
+    font-size: 1.5em;
+}
+
+h3 {
+    font-size: 1.4em;
+}
+
+h4 {
+    font-size: 1.3em;
+}
+
+h5 {
+    font-size: 1.2em;
+}
+
+h6 {
+    font-size: 1.1em;
+}
+
+ul {
+    list-style-type: disc;
+}
+
+code, tt {
+    font-size: 1.2em;
+}
+
+dt code {
+    font-size: 1.2em;
+}
+
+table tr td dt code {
+    font-size: 1.2em;
+    vertical-align: top;
+}
+
+sup {
+    font-size: .6em;
+}
+
+/*
+Document title and Copyright styles
+*/
+.clear {
+    clear: both;
+    height: 0px;
+    overflow: hidden;
+}
+
+.aboutLanguage {
+    float: right;
+    padding: 0px 21px;
+    font-size: .8em;
+    z-index: 200;
+    margin-top: -7px;
+}
+
+.legalCopy {
+    margin-left: .5em;
+}
+
+.bar a, .bar a:link, .bar a:visited, .bar a:active {
+    color: #FFFFFF;
+    text-decoration: none;
+}
+
+.bar a:hover, .bar a:focus {
+    color: #bb7a2a;
+}
+
+.tab {
+    background-color: #0066FF;
+    background-image: url(resources/titlebar.gif);
+    background-position: left top;
+    background-repeat: no-repeat;
+    color: #ffffff;
+    padding: 8px;
+    width: 5em;
+    font-weight: bold;
+}
+
+/*
+Navigation bar styles
+*/
+.bar {
+    background-image: url(resources/background.gif);
+    background-repeat: repeat-x;
+    color: #FFFFFF;
+    padding: .8em .5em .4em .8em;
+    height: auto; /*height:1.8em;*/
+    font-size: 1em;
+    margin: 0;
+}
+
+.topNav {
+    background-image: url(resources/background.gif);
+    background-repeat: repeat-x;
+    color: #FFFFFF;
+    float: left;
+    padding: 0;
+    width: 100%;
+    clear: right;
+    height: 2.8em;
+    padding-top: 10px;
+    overflow: hidden;
+}
+
+.bottomNav {
+    margin-top: 10px;
+    background-image: url(resources/background.gif);
+    background-repeat: repeat-x;
+    color: #FFFFFF;
+    float: left;
+    padding: 0;
+    width: 100%;
+    clear: right;
+    height: 2.8em;
+    padding-top: 10px;
+    overflow: hidden;
+}
+
+.subNav {
+    background-color: #dee3e9;
+    border-bottom: 1px solid #9eadc0;
+    float: left;
+    width: 100%;
+    overflow: hidden;
+}
+
+.subNav div {
+    clear: left;
+    float: left;
+    padding: 0 0 5px 6px;
+}
+
+ul.navList, ul.subNavList {
+    float: left;
+    margin: 0 25px 0 0;
+    padding: 0;
+}
+
+ul.navList li {
+    list-style: none;
+    float: left;
+    padding: 3px 6px;
+}
+
+ul.subNavList li {
+    list-style: none;
+    float: left;
+    font-size: 90%;
+}
+
+.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited {
+    color: #FFFFFF;
+    text-decoration: none;
+}
+
+.topNav a:hover, .bottomNav a:hover {
+    text-decoration: none;
+    color: #bb7a2a;
+}
+
+.navBarCell1Rev {
+    background-image: url(resources/tab.gif);
+    background-color: #a88834;
+    color: #FFFFFF;
+    margin: auto 5px;
+    border: 1px solid #c9aa44;
+}
+
+/*
+Page header and footer styles
+*/
+.header, .footer {
+    clear: both;
+    margin: 0 20px;
+    padding: 5px 0 0 0;
+}
+
+.indexHeader {
+    margin: 10px;
+    position: relative;
+}
+
+.indexHeader h1 {
+    font-size: 1.3em;
+}
+
+.title {
+    color: #2c4557;
+    margin: 10px 0;
+}
+
+.subTitle {
+    margin: 5px 0 0 0;
+}
+
+.header ul {
+    margin: 0 0 25px 0;
+    padding: 0;
+}
+
+.footer ul {
+    margin: 20px 0 5px 0;
+}
+
+.header ul li, .footer ul li {
+    list-style: none;
+    font-size: 1.2em;
+}
+
+/*
+Heading styles
+*/
+div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 {
+    background-color: #dee3e9;
+    border-top: 1px solid #9eadc0;
+    border-bottom: 1px solid #9eadc0;
+    margin: 0 0 6px -8px;
+    padding: 2px 5px;
+}
+
+ul.blockList ul.blockList ul.blockList li.blockList h3 {
+    background-color: #dee3e9;
+    border-top: 1px solid #9eadc0;
+    border-bottom: 1px solid #9eadc0;
+    margin: 0 0 6px -8px;
+    padding: 2px 5px;
+}
+
+ul.blockList ul.blockList li.blockList h3 {
+    padding: 0;
+    margin: 15px 0;
+}
+
+ul.blockList li.blockList h2 {
+    padding: 0px 0 20px 0;
+}
+
+/*
+Page layout container styles
+*/
+.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer {
+    clear: both;
+    padding: 10px 20px;
+    position: relative;
+}
+
+.indexContainer {
+    margin: 10px;
+    position: relative;
+    font-size: 1.0em;
+}
+
+.indexContainer h2 {
+    font-size: 1.1em;
+    padding: 0 0 3px 0;
+}
+
+.indexContainer ul {
+    margin: 0;
+    padding: 0;
+}
+
+.indexContainer ul li {
+    list-style: none;
+}
+
+.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt {
+    font-size: 1.1em;
+    font-weight: bold;
+    margin: 10px 0 0 0;
+    color: #4E4E4E;
+}
+
+.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd {
+    margin: 10px 0 10px 20px;
+}
+
+.serializedFormContainer dl.nameValue dt {
+    margin-left: 1px;
+    font-size: 1.1em;
+    display: inline;
+    font-weight: bold;
+}
+
+.serializedFormContainer dl.nameValue dd {
+    margin: 0 0 0 1px;
+    font-size: 1.1em;
+    display: inline;
+}
+
+/*
+List styles
+*/
+ul.horizontal li {
+    display: inline;
+    font-size: 0.9em;
+}
+
+ul.inheritance {
+    margin: 0;
+    padding: 0;
+}
+
+ul.inheritance li {
+    display: inline;
+    list-style: none;
+}
+
+ul.inheritance li ul.inheritance {
+    margin-left: 15px;
+    padding-left: 15px;
+    padding-top: 1px;
+}
+
+ul.blockList, ul.blockListLast {
+    margin: 10px 0 10px 0;
+    padding: 0;
+}
+
+ul.blockList li.blockList, ul.blockListLast li.blockList {
+    list-style: none;
+    margin-bottom: 25px;
+}
+
+ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList {
+    padding: 0px 20px 5px 10px;
+    border: 1px solid #9eadc0;
+    background-color: #f9f9f9;
+}
+
+ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList {
+    padding: 0 0 5px 8px;
+    background-color: #ffffff;
+    border: 1px solid #9eadc0;
+    border-top: none;
+}
+
+ul.blockList ul.blockList ul.blockList ul.blockList li.blockList {
+    margin-left: 0;
+    padding-left: 0;
+    padding-bottom: 15px;
+    border: none;
+    border-bottom: 1px solid #9eadc0;
+}
+
+ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast {
+    list-style: none;
+    border-bottom: none;
+    padding-bottom: 0;
+}
+
+table tr td dl, table tr td dl dt, table tr td dl dd {
+    margin-top: 0;
+    margin-bottom: 1px;
+}
+
+/*
+Table styles
+*/
+.contentContainer table, .classUseContainer table, .constantValuesContainer table {
+    border-bottom: 1px solid #9eadc0;
+    width: 100%;
+}
+
+.contentContainer ul li table, .classUseContainer ul li table, .constantValuesContainer ul li table {
+    width: 100%;
+}
+
+.contentContainer .description table, .contentContainer .details table {
+    border-bottom: none;
+}
+
+.contentContainer ul li table th.colOne, .contentContainer ul li table th.colFirst, .contentContainer ul li table th.colLast, .classUseContainer ul li table th, .constantValuesContainer ul li table th, .contentContainer ul li table td.colOne, .contentContainer ul li table td.colFirst, .contentContainer ul li table td.colLast, .classUseContainer ul li table td, .constantValuesContainer ul li table td {
+    vertical-align: top;
+    padding-right: 20px;
+}
+
+.contentContainer ul li table th.colLast, .classUseContainer ul li table th.colLast, .constantValuesContainer ul li table th.colLast,
+.contentContainer ul li table td.colLast, .classUseContainer ul li table td.colLast, .constantValuesContainer ul li table td.colLast,
+.contentContainer ul li table th.colOne, .classUseContainer ul li table th.colOne,
+.contentContainer ul li table td.colOne, .classUseContainer ul li table td.colOne {
+    padding-right: 3px;
+}
+
+.overviewSummary caption, .packageSummary caption, .contentContainer ul.blockList li.blockList caption, .summary caption, .classUseContainer caption, .constantValuesContainer caption {
+    position: relative;
+    text-align: left;
+    background-repeat: no-repeat;
+    color: #FFFFFF;
+    font-weight: bold;
+    clear: none;
+    overflow: hidden;
+    padding: 0px;
+    margin: 0px;
+}
+
+caption a:link, caption a:hover, caption a:active, caption a:visited {
+    color: #FFFFFF;
+}
+
+.overviewSummary caption span, .packageSummary caption span, .contentContainer ul.blockList li.blockList caption span, .summary caption span, .classUseContainer caption span, .constantValuesContainer caption span {
+    white-space: nowrap;
+    padding-top: 8px;
+    padding-left: 8px;
+    display: block;
+    float: left;
+    background-image: url(resources/titlebar.gif);
+    height: 18px;
+}
+
+.contentContainer ul.blockList li.blockList caption span.activeTableTab span {
+    white-space: nowrap;
+    padding-top: 8px;
+    padding-left: 8px;
+    display: block;
+    float: left;
+    background-image: url(resources/activetitlebar.gif);
+    height: 18px;
+}
+
+.contentContainer ul.blockList li.blockList caption span.tableTab span {
+    white-space: nowrap;
+    padding-top: 8px;
+    padding-left: 8px;
+    display: block;
+    float: left;
+    background-image: url(resources/titlebar.gif);
+    height: 18px;
+}
+
+.contentContainer ul.blockList li.blockList caption span.tableTab, .contentContainer ul.blockList li.blockList caption span.activeTableTab {
+    padding-top: 0px;
+    padding-left: 0px;
+    background-image: none;
+    float: none;
+    display: inline;
+}
+
+.overviewSummary .tabEnd, .packageSummary .tabEnd, .contentContainer ul.blockList li.blockList .tabEnd, .summary .tabEnd, .classUseContainer .tabEnd, .constantValuesContainer .tabEnd {
+    width: 10px;
+    background-image: url(resources/titlebar_end.gif);
+    background-repeat: no-repeat;
+    background-position: top right;
+    position: relative;
+    float: left;
+}
+
+.contentContainer ul.blockList li.blockList .activeTableTab .tabEnd {
+    width: 10px;
+    margin-right: 5px;
+    background-image: url(resources/activetitlebar_end.gif);
+    background-repeat: no-repeat;
+    background-position: top right;
+    position: relative;
+    float: left;
+}
+
+.contentContainer ul.blockList li.blockList .tableTab .tabEnd {
+    width: 10px;
+    margin-right: 5px;
+    background-image: url(resources/titlebar_end.gif);
+    background-repeat: no-repeat;
+    background-position: top right;
+    position: relative;
+    float: left;
+}
+
+ul.blockList ul.blockList li.blockList table {
+    margin: 0 0 12px 0px;
+    width: 100%;
+}
+
+.tableSubHeadingColor {
+    background-color: #EEEEFF;
+}
+
+.altColor {
+    background-color: #eeeeef;
+}
+
+.rowColor {
+    background-color: #ffffff;
+}
+
+.overviewSummary td, .packageSummary td, .contentContainer ul.blockList li.blockList td, .summary td, .classUseContainer td, .constantValuesContainer td {
+    text-align: left;
+    padding: 3px 3px 3px 7px;
+}
+
+th.colFirst, th.colLast, th.colOne, .constantValuesContainer th {
+    background: #dee3e9;
+    border-top: 1px solid #9eadc0;
+    border-bottom: 1px solid #9eadc0;
+    text-align: left;
+    padding: 3px 3px 3px 7px;
+}
+
+td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover {
+    font-weight: bold;
+}
+
+td.colFirst, th.colFirst {
+    border-left: 1px solid #9eadc0;
+    white-space: nowrap;
+}
+
+td.colLast, th.colLast {
+    border-right: 1px solid #9eadc0;
+}
+
+td.colOne, th.colOne {
+    border-right: 1px solid #9eadc0;
+    border-left: 1px solid #9eadc0;
+}
+
+table.overviewSummary {
+    padding: 0px;
+    margin-left: 0px;
+}
+
+table.overviewSummary td.colFirst, table.overviewSummary th.colFirst,
+table.overviewSummary td.colOne, table.overviewSummary th.colOne {
+    width: 25%;
+    vertical-align: middle;
+}
+
+table.packageSummary td.colFirst, table.overviewSummary th.colFirst {
+    width: 25%;
+    vertical-align: middle;
+}
+
+/*
+Content styles
+*/
+.description pre {
+    margin-top: 0;
+}
+
+.deprecatedContent {
+    margin: 0;
+    padding: 10px 0;
+}
+
+.docSummary {
+    padding: 0;
+}
+
+/*
+Formatting effect styles
+*/
+.sourceLineNo {
+    color: green;
+    padding: 0 30px 0 0;
+}
+
+h1.hidden {
+    visibility: hidden;
+    overflow: hidden;
+    font-size: .9em;
+}
+
+.block {
+    display: block;
+    margin: 3px 0 0 0;
+}
+
+.strong {
+    font-weight: bold;
+}
+
+/*
+Spring
+*/
+
+pre.code {
+    background-color: #F8F8F8;
+    border: 1px solid #CCCCCC;
+    border-radius: 3px 3px 3px 3px;
+    overflow: auto;
+    padding: 10px;
+    margin: 4px 20px 2px 0px;
+}
+
+pre.code code, pre.code code * {
+    font-size: 1em;
+}
+
+pre.code code, pre.code code * {
+    padding: 0 !important;
+    margin: 0 !important;
+}
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/projectreactor.git



More information about the pkg-java-commits mailing list