[gradle-1.12] 61/211: Upstream import 1.1

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Wed Jul 1 14:18:03 UTC 2015


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

seamlik-guest pushed a commit to branch master
in repository gradle-1.12.

commit 8eb6e8d0312ca2ee9142ff989cf4cee1472d2041
Author: Miguel Landaeta <miguel at miguel.cc>
Date:   Sat Aug 4 17:55:34 2012 -0430

    Upstream import 1.1
---
 build.gradle                                       | 274 +++----
 buildSrc/build.gradle                              |  13 +-
 .../org/gradle/build/GenerateReleasesXml.groovy    |  42 --
 .../src/main/groovy/org/gradle/build/Git.groovy    |  55 --
 .../main/groovy/org/gradle/build/Install.groovy    |   8 +-
 .../src/main/groovy/org/gradle/build/JarJar.groovy |  66 ++
 .../main/groovy/org/gradle/build/Releases.groovy   |  90 ---
 .../org/gradle/build/TestReportAggregator.groovy   |  51 ++
 .../main/groovy/org/gradle/build/Version.groovy    | 151 ----
 .../build/docs/dsl/ExtractDslMetaDataTask.groovy   |  12 +-
 .../build/docs/dsl/SourceMetaDataVisitor.java      |  25 +-
 .../gradle/build/docs/dsl/docbook/BlockDoc.groovy  |  12 +-
 .../gradle/build/docs/dsl/docbook/ClassDoc.groovy  |  14 +-
 .../build/docs/dsl/docbook/ClassDocRenderer.groovy |  29 +-
 .../build/docs/dsl/docbook/DslElementDoc.java      |  33 +
 .../gradle/build/docs/dsl/docbook/MethodDoc.groovy |  12 +-
 .../build/docs/dsl/docbook/PropertyDoc.groovy      |  10 +-
 .../docs/dsl/model/AbstractLanguageElement.java    |  65 ++
 .../gradle/build/docs/dsl/model/ClassMetaData.java |  26 +-
 .../build/docs/dsl/model/LanguageElement.java      |   8 +
 .../build/docs/dsl/model/MethodMetaData.java       |  15 +-
 .../build/docs/dsl/model/PropertyMetaData.java     |  13 +-
 .../build/integtest/IntegTestConvention.groovy     |  49 ++
 .../gradle/build/integtest/IntegTestPlugin.groovy  |  26 +
 .../groovy/org/gradle/build/ReleasesTest.groovy    | 116 ---
 .../docs/dsl/ExtractDslMetaDataTaskTest.groovy     |  93 ++-
 .../build/docs/dsl/model/ClassMetaDataTest.groovy  |  41 +
 .../build/docs/dsl/model/MethodMetaDataTest.groovy |  33 +
 .../docs/dsl/model/ParameterMetaDataTest.groovy    |   7 +-
 .../docs/dsl/model/PropertyMetaDataTest.groovy     |  32 +
 .../gradle/test/GroovyClassWithAnnotation.groovy   |  10 +
 .../org/gradle/test/JavaClassWithAnnotation.java   |  10 +
 .../gradle/test/JavaInterfaceWithAnnotation.java   |  26 +
 config/checkstyle/checkstyle.xml                   |   3 +
 config/checkstyle/suppressions.xml                 |   7 +
 config/codenarc.xml                                |   2 +-
 gradle/classycle.gradle                            |  17 +-
 gradle/groovyProject.gradle                        |   9 +-
 gradle/idea.gradle                                 |  88 ++-
 gradle/integTest.gradle                            |  63 +-
 gradle/publish.gradle                              |   3 +-
 gradle/testFixtures.gradle                         |   2 +-
 gradle/versioning.gradle                           |  54 ++
 gradle/wrapper/gradle-wrapper.properties           |   4 +-
 gradlew                                            |   2 +-
 settings.gradle                                    |   4 +-
 .../internal/DefaultAnnouncerFactory.groovy        |   7 +-
 .../src/main/java/org/gradle/api/Action.java       |  30 +
 .../src/main/java/org/gradle/api/Experimental.java |  30 +
 .../src/main/java/org/gradle/api/JavaVersion.java  | 114 +++
 .../src/main/java/org/gradle/api/Nullable.java     |  26 +
 .../main/java/org/gradle/api/internal/Factory.java |   2 +-
 .../api/internal/project/ServiceRegistry.java      |   2 +-
 .../java/org/gradle/internal/TimeProvider.java     |  22 +
 .../java/org/gradle/internal/TrueTimeProvider.java |  25 +
 .../org/gradle/internal/classpath/ClassPath.java   |  41 +
 .../internal/classpath/DefaultClassPath.java       | 121 +++
 .../gradle/internal/concurrent/AsyncStoppable.java |  37 +
 .../concurrent/DefaultExecutorFactory.java         | 128 ++++
 .../internal/concurrent/ExecutorFactory.java       |  27 +
 .../internal/concurrent/StoppableExecutor.java     |  35 +
 .../gradle/internal/concurrent/Synchronizer.java   |  45 ++
 .../gradle/internal/id/CompositeIdGenerator.java   |  66 ++
 .../java/org/gradle/internal/id/IdGenerator.java   |  29 +
 .../org/gradle/internal/id/LongIdGenerator.java    |  27 +
 .../gradle/internal/id/RandomLongIdGenerator.java  |  27 +
 .../java/org/gradle/internal/id/UUIDGenerator.java |  24 +
 .../internal/io/ClassLoaderObjectInputStream.java  |  43 ++
 .../src/main/java/org/gradle/internal/jvm/Jvm.java |  27 +-
 .../internal/reflect/DirectInstantiator.java       |  68 ++
 .../org/gradle/internal/reflect/Instantiator.java  |  28 +
 .../internal/reflect/JavaReflectionUtil.java       |  83 +++
 .../internal/service/DefaultServiceRegistry.java   |   6 +-
 .../gradle/internal/service/ServiceLocator.java    | 145 ++++
 .../internal/service/ServiceLookupException.java   |  30 +
 .../gradle/internal/service/ServiceRegistry.java   |  11 +-
 .../service/SynchronizedServiceRegistry.java       |  68 ++
 .../groovy/org/gradle/api/JavaVersionSpec.groovy   | 169 +++++
 .../internal/classpath/DefaultClassPathTest.groovy |  77 ++
 .../concurrent/DefaultExecutorFactorySpec.groovy   |  48 ++
 .../concurrent/DefaultExecutorFactoryTest.groovy   | 131 ++++
 .../internal/id/CompositeIdGeneratorTest.groovy    |  82 ++
 .../gradle/internal/id/LongIdGeneratorTest.groovy  |  51 ++
 .../org/gradle/internal/jvm/AppleJvmTest.groovy    |   6 +-
 .../groovy/org/gradle/internal/jvm/JvmTest.groovy  |  22 +-
 .../internal/reflect/DirectInstantiatorTest.groovy | 197 +++++
 .../internal/reflect/JavaReflectionUtilTest.groovy |  80 ++
 .../service/DefaultServiceRegistryTest.java        |   6 +-
 .../internal/service/ServiceLocatorTest.groovy     | 182 +++++
 .../service/SynchronizedServiceRegistryTest.groovy |  44 ++
 .../quality/CheckstylePluginIntegrationTest.groovy |  29 +-
 .../quality/CodeNarcPluginIntegrationTest.groovy   |  32 +-
 .../CodeQualityPluginIntegrationTest.groovy        |   2 +-
 .../quality/FindBugsPluginIntegrationTest.groovy   |  59 +-
 .../quality/PmdPluginIntegrationTest.groovy        |  53 +-
 .../gradle/api/plugins/quality/Checkstyle.groovy   |  19 +-
 .../org/gradle/api/plugins/quality/CodeNarc.groovy |  21 +-
 .../org/gradle/api/plugins/quality/FindBugs.groovy |  33 +-
 .../org/gradle/api/plugins/quality/JDepend.groovy  |   2 +-
 .../org/gradle/api/plugins/quality/Pmd.groovy      |  21 +-
 .../quality/internal/findbugs/FindBugsDaemon.java  |  23 -
 .../internal/findbugs/FindBugsDaemonClient.java    |  43 --
 .../findbugs/FindBugsDaemonClientProtocol.java     |  21 -
 .../internal/findbugs/FindBugsDaemonManager.groovy |  61 --
 .../internal/findbugs/FindBugsDaemonServer.java    |  50 --
 .../internal/findbugs/FindBugsExecuter.java        |  20 +-
 .../quality/internal/findbugs/FindBugsResult.java  |  10 +
 .../internal/findbugs/FindBugsWorkerClient.java    |  43 ++
 .../findbugs/FindBugsWorkerClientProtocol.java     |  21 +
 .../internal/findbugs/FindBugsWorkerManager.groovy |  48 ++
 .../internal/findbugs/FindBugsWorkerServer.java    |  50 ++
 .../gradle/api/plugins/quality/FindBugsTest.groovy |   4 +-
 subprojects/core-impl/core-impl.gradle             |  31 +-
 .../DefaultDependencyManagementServices.java       |  33 +-
 .../artifacts/DefaultResolvedArtifact.java         |   8 +-
 .../dsl/DefaultPublishArtifactFactory.java         |   2 +-
 .../ivyservice/DefaultCacheLockingManager.java     |   2 +-
 .../ivyservice/DefaultIvyDependencyPublisher.java  | 135 +++-
 .../ivyservice/DefaultLenientConfiguration.java    |  74 +-
 .../ivyservice/DefaultPublishOptionsFactory.java   |  41 -
 .../ivyservice/DefaultUnresolvedDependency.java    |  21 +-
 .../ivyservice/IvyBackedArtifactPublisher.java     |   2 +-
 .../ivyservice/IvyDependencyPublisher.java         |   4 +-
 .../ivyservice/PublishOptionsFactory.java          |  28 -
 .../ivyservice/ResolvedArtifactFactory.java        |   7 +-
 ...cuitEmptyConfigsArtifactDependencyResolver.java |   4 +
 .../DefaultCachedModuleResolution.java             |   2 +-
 .../ModuleResolutionCacheEntry.java                |   2 +-
 .../SingleFileBackedModuleResolutionCache.java     |   2 +-
 .../ivyresolve/CachingModuleVersionRepository.java |   2 +-
 .../ivyservice/ivyresolve/ResolveIvyFactory.java   |   2 +-
 .../DownloadedIvyModuleDescriptorParser.java       |  36 +
 .../parser/GradlePomModuleDescriptorBuilder.java   |  43 +-
 .../parser/GradlePomModuleDescriptorParser.java    |  11 -
 .../ivyresolve/parser/ParserRegistry.java          |   5 +-
 .../modulecache/DefaultCachedModuleDescriptor.java |   2 +-
 .../modulecache/DefaultModuleDescriptorCache.java  |   2 +-
 .../modulecache/ModuleDescriptorCacheEntry.java    |   2 +-
 ...efaultArtifactsToModuleDescriptorConverter.java |   2 +-
 .../DefaultProjectModuleRegistry.java              |   4 +-
 .../projectmodule/ProjectDependencyResolver.java   |   2 +-
 .../resolveengine/DependencyGraphBuilder.java      |   2 +-
 .../CannotLocateLocalMavenRepositoryException.java |  10 +-
 .../DefaultLocalMavenRepositoryLocator.java        |  55 +-
 .../mvnsettings/DefaultMavenFileLocations.java     |   9 +-
 .../DefaultExternalResourceRepository.java         |  30 +-
 .../repositories/DefaultResolverFactory.java       |   6 +-
 .../repositories/ExternalResourceResolver.java     |   6 +-
 .../file/FileExternalResourceRepository.java       |  48 --
 .../externalresource/AbstractExternalResource.java |  21 +-
 .../externalresource/ExternalResource.java         |   3 +
 .../ExternalResourceIvyResourceAdapter.java        |  79 --
 .../LocalFileStandInExternalResource.java          |   4 -
 .../cached/ByUrlCachedExternalResourceIndex.java   |   2 +-
 .../cached/DefaultCachedExternalResourceIndex.java |   4 +-
 ...actAtRepositoryCachedExternalResourceIndex.java |   2 +-
 .../LocalMavenLocallyAvailableResourceFinder.java  |  84 +++
 .../ivy/LocallyAvailableResourceFinderFactory.java |  17 +-
 ...PatternBasedLocallyAvailableResourceFinder.java |  56 +-
 .../local/ivy/PatternTransformer.java              |  78 ++
 .../metadata/ExternalResourceMetaDataCompare.java  |   5 -
 .../transport/file/FileResourceConnector.java      |  85 +++
 .../transport/file/FileTransport.java              |   7 +-
 .../http/ApacheDirectoryListingParser.java         | 132 ++++
 .../http/CopyProgressListenerAdapter.java          |  31 +
 .../transport/http/HttpClientConfigurer.java       |  68 +-
 .../transport/http/HttpClientHelper.java           |  22 +-
 .../transport/http/HttpResourceLister.java         |  68 +-
 .../transport/http/HttpResponseResource.java       |  10 +
 .../transport/http/HttpTransport.java              |   3 +-
 .../internal/filestore/UniquePathFileStore.java    |   2 +-
 .../notations/ClientModuleNotationParser.java      |   2 +-
 .../DependencyClassPathNotationParser.java         |   2 +-
 .../notations/DependencyFilesNotationParser.java   |   2 +-
 .../notations/DependencyMapNotationParser.java     |   2 +-
 .../notations/DependencyProjectNotationParser.java |   2 +-
 .../notations/DependencyStringNotationParser.java  |   2 +-
 .../notations/ProjectDependencyFactory.java        |   2 +-
 .../gradle/api/artifacts/ArtifactsTestUtils.java   |   8 +-
 .../DefaultDependencyManagementServicesTest.groovy |   4 +-
 .../artifacts/DefaultResolvedArtifactTest.groovy   |   7 +-
 .../dsl/DefaultPublishArtifactFactoryTest.groovy   |   2 +-
 .../DefaultIvyDependencyPublisherTest.java         |  65 --
 .../DefaultPublishOptionsFactoryTest.java          |  58 --
 .../DefaultUnresolvedDependencySpec.groovy         |  38 +
 .../ivyservice/IvyBackedArtifactPublisherTest.java |  24 +-
 .../CachingModuleVersionRepositoryTest.groovy      |   2 +-
 .../DependencyResolverIdentifierTest.groovy        |  20 +-
 .../DownloadedIvyModuleDescriptorParserTest.groovy |  47 ++
 .../GradlePomModuleDescriptorParserTest.groovy     | 183 +++++
 .../ivyresolve/parser/PomParserTest.groovy         | 183 -----
 ...ltArtifactsToModuleDescriptorConverterTest.java |   2 +-
 .../DependencyGraphBuilderTest.groovy              |  14 +-
 .../DefaultLocalMavenRepositoryLocatorTest.groovy  |  43 ++
 .../DefaultIvyArtifactRepositoryTest.groovy        |   3 +-
 .../repositories/DefaultResolverFactoryTest.groovy |   2 +-
 .../DefaultArtifactResolutionCacheTest.groovy      |   2 +-
 .../http/ApacheDirectoryListingParserTest.groovy   | 146 ++++
 .../transport/http/HttpClientConfigurerTest.groovy |  28 +-
 .../transport/http/HttpResourceListerTest.groovy   |  44 ++
 .../transport/http/HttpResponseResourceTest.groovy |  19 +-
 .../transport/http/ntlm/NTLMCredentialsTest.groovy |  18 +-
 .../DependencyClassPathNotationParserTest.groovy   |   4 +-
 .../DependencyMapNotationParserTest.groovy         |   2 +-
 .../DependencyStringNotationParserTest.groovy      |   2 +-
 .../notations/ProjectDependencyFactoryTest.groovy  |   2 +-
 .../transport/http/artifactory_dirlisting.html     |  36 +
 .../transport/http/mavencentral_dirlisting.html    |  30 +
 .../transport/http/nexus_dirlisting.html           | 331 +++++++++
 subprojects/core/core.gradle                       |  14 +-
 .../api/dsl/DynamicObjectIntegrationTest.groovy    | 496 +++++++++++++
 .../gradle/api/tasks/ArchiveIntegrationTest.groovy | 531 +++++++++++++
 .../ArchiveTaskPermissionsIntegrationTest.groovy   | 227 +++---
 .../tasks/CopyPermissionsIntegrationTest.groovy    | 142 +++-
 .../api/tasks/CopyTaskIntegrationSpec.groovy       |  70 ++
 .../scripts/StatementLabelsIntegrationTest.groovy  |  89 +++
 .../compressedTarWithWrongExtension.tar            | Bin
 .../src/main/groovy/org/gradle/CacheUsage.java     |   2 +-
 .../src/main/groovy/org/gradle/RefreshOptions.java |   2 +-
 .../src/main/groovy/org/gradle/StartParameter.java |  21 +-
 .../src/main/groovy/org/gradle/api/Action.java     |  30 -
 .../main/groovy/org/gradle/api/JavaVersion.java    |  78 --
 .../src/main/groovy/org/gradle/api/Nullable.java   |  27 -
 .../src/main/groovy/org/gradle/api/Project.java    |  27 +-
 .../api/artifacts/ExternalModuleDependency.java    |   2 +-
 .../gradle/api/artifacts/LenientConfiguration.java |  13 +-
 .../gradle/api/artifacts/UnresolvedDependency.java |  10 +
 .../artifacts/cache/ArtifactResolutionControl.java |   2 +
 .../cache/DependencyResolutionControl.java         |   2 +
 .../artifacts/cache/ModuleResolutionControl.java   |   2 +
 .../api/artifacts/cache/ResolutionControl.java     |   3 +
 .../api/artifacts/cache/ResolutionRules.java       |   2 +
 .../groovy/org/gradle/api/file/RelativePath.java   | 399 +++++-----
 .../api/file/UnableToDeleteFileException.java      |  43 ++
 .../org/gradle/api/initialization/Settings.java    |   2 +-
 .../api/internal/AbstractClassGenerator.java       |  15 +-
 .../AbstractNamedDomainObjectContainer.java        |   1 +
 .../org/gradle/api/internal/AbstractTask.java      |   7 +-
 .../api/internal/AsmBackedClassGenerator.java      |  40 +-
 .../internal/ClassGeneratorBackedInstantiator.java |   2 +
 .../org/gradle/api/internal/ClassPathProvider.java |   2 +-
 .../org/gradle/api/internal/ClassPathRegistry.java |   2 +-
 .../api/internal/DefaultClassPathProvider.java     |   4 +-
 .../api/internal/DefaultClassPathRegistry.java     |   2 +-
 .../DefaultNamedDomainObjectCollection.java        |   1 +
 .../api/internal/DefaultNamedDomainObjectList.java |   1 +
 .../api/internal/DefaultNamedDomainObjectSet.java  |   1 +
 .../api/internal/DependencyClassPathProvider.java  |   4 +-
 .../gradle/api/internal/DirectInstantiator.java    |  67 --
 .../internal/DynamicModulesClassPathProvider.java  |   4 +-
 .../gradle/api/internal/DynamicObjectHelper.java   |  37 +-
 .../api/internal/ExtensibleDynamicObject.java      |   3 +-
 .../FactoryNamedDomainObjectContainer.java         |   1 +
 .../org/gradle/api/internal/Instantiator.java      |  28 -
 .../org/gradle/api/internal/NoDynamicObject.java   |  29 -
 .../groovy/org/gradle/api/internal/Operation.java  |  26 -
 .../ReflectiveNamedDomainObjectFactory.java        |   2 +
 .../api/internal/ThreadGlobalInstantiator.java     |   3 +
 .../DefaultArtifactRepositoryContainer.java        |   2 +-
 .../artifacts/DependencyResolutionServices.java    |   2 +
 .../DefaultConfigurationContainer.java             |   2 +-
 .../artifacts/dsl/AbstractScriptTransformer.java   |  64 --
 .../dsl/BuildScriptClasspathScriptTransformer.java |  38 -
 .../artifacts/dsl/BuildScriptTransformer.java      |  40 -
 .../artifacts/dsl/ClasspathScriptTransformer.java  | 177 -----
 .../artifacts/dsl/DefaultRepositoryHandler.java    |   2 +-
 .../artifacts/dsl/FixMainScriptTransformer.java    |  51 --
 .../dsl/TaskDefinitionScriptTransformer.java       | 194 -----
 .../api/internal/cache/CacheAccessSerializer.java  |   2 +-
 .../internal/changedetection/CachingHasher.java    |   2 +-
 .../DefaultTaskArtifactStateCacheAccess.java       |   2 +-
 .../changedetection/OutputFilesSnapshotter.java    |   2 +-
 .../TaskArtifactStateCacheAccess.java              |   2 +-
 .../internal/classpath/DefaultModuleRegistry.java  |   4 +-
 .../api/internal/classpath/EffectiveClassPath.java |   2 +-
 .../org/gradle/api/internal/classpath/Module.java  |   2 +-
 .../concurrent/SynchronizedServiceRegistry.java    |  57 --
 .../api/internal/concurrent/Synchronizer.java      |  46 --
 .../api/internal/file/AbstractFileResolver.java    |  11 +-
 .../file/DefaultTemporaryFileProvider.java         |  13 +-
 .../org/gradle/api/internal/file/FileResolver.java |   3 +-
 .../org/gradle/api/internal/file/FileSource.java   |  23 -
 .../org/gradle/api/internal/file/RelativeFile.java |  42 ++
 .../internal/file/TmpDirTemporaryFileProvider.java |   5 +-
 .../api/internal/file/copy/CopyActionImpl.java     |   3 +-
 .../api/internal/file/copy/DeleteActionImpl.java   |  56 +-
 .../internal/file/copy/MappingCopySpecVisitor.java |  24 +-
 .../api/internal/plugins/DefaultConvention.java    |   4 +-
 .../api/internal/project/AbstractProject.java      |   1 +
 .../project/DefaultIsolatedAntBuilder.groovy       |   4 +-
 .../internal/project/GlobalServicesRegistry.java   |   2 +
 .../api/internal/project/ProjectFactory.java       |   2 +-
 .../project/ProjectInternalServiceRegistry.java    |   6 +-
 .../internal/project/TaskExecutionServices.java    |   2 +-
 .../project/TopLevelBuildServiceRegistry.java      |   9 +-
 .../gradle/api/internal/specs/ExplainingSpec.java  |  35 +
 .../gradle/api/internal/specs/ExplainingSpecs.java |  49 ++
 .../api/internal/tasks/DefaultTaskCollection.java  |   2 +-
 .../api/internal/tasks/DefaultTaskContainer.java   |   2 +-
 .../tasks/DefaultTaskContainerFactory.java         |   2 +-
 .../groovy/org/gradle/api/logging/LogLevel.java    |   4 +-
 .../main/groovy/org/gradle/api/specs/Specs.java    |  10 +-
 .../org/gradle/api/specs/internal/ClosureSpec.java |  35 +
 .../org/gradle/api/tasks/AbstractCopyTask.java     |   2 +-
 .../main/groovy/org/gradle/api/tasks/Input.java    |  60 +-
 .../org/gradle/api/tasks/InputDirectory.java       |   6 +-
 .../groovy/org/gradle/api/tasks/InputFile.java     |   6 +-
 .../groovy/org/gradle/api/tasks/InputFiles.java    |   6 +-
 .../main/groovy/org/gradle/api/tasks/JavaExec.java |   2 +-
 .../main/groovy/org/gradle/api/tasks/Optional.java |   6 +-
 .../org/gradle/api/tasks/OutputDirectories.java    |   6 +-
 .../org/gradle/api/tasks/OutputDirectory.java      |   6 +-
 .../groovy/org/gradle/api/tasks/OutputFile.java    |   6 +-
 .../groovy/org/gradle/api/tasks/OutputFiles.java   |   6 +-
 .../groovy/org/gradle/api/tasks/SkipWhenEmpty.java |   6 +-
 .../gradle/api/tasks/util/PatternFilterable.java   |  18 +-
 .../groovy/org/gradle/cache/DefaultSerializer.java |   3 +-
 .../org/gradle/cache/ObjectCacheBuilder.java       |   2 +
 .../groovy/org/gradle/cache/PersistentCache.java   |   2 +
 .../main/groovy/org/gradle/cache/Serializer.java   |  25 -
 .../gradle/cache/internal/AbstractFileAccess.java  |   4 +-
 .../org/gradle/cache/internal/CacheFactory.java    |   1 +
 .../gradle/cache/internal/DefaultCacheAccess.java  |  14 +-
 .../gradle/cache/internal/DefaultCacheFactory.java |  24 +-
 .../cache/internal/DefaultCacheRepository.java     |   1 +
 .../cache/internal/DefaultFileLockManager.java     | 124 ++--
 .../internal/DefaultPersistentDirectoryCache.java  |  52 +-
 .../internal/DefaultPersistentDirectoryStore.java  |  42 +-
 .../DelegateOnDemandPersistentDirectoryCache.java  | 110 +++
 .../org/gradle/cache/internal/FileAccess.java      |  34 +-
 .../internal/FileIntegrityViolationException.java  |  27 +
 ...onSuppressingPersistentStateCacheDecorator.java |  49 ++
 .../groovy/org/gradle/cache/internal/FileLock.java |  11 +-
 .../internal/InsufficientLockModeException.java    |  23 +
 .../MultiProcessSafePersistentIndexedCache.java    |  28 +-
 .../gradle/cache/internal/OnDemandFileAccess.java  |  17 +-
 .../internal/ReferencablePersistentCache.java      |  26 +
 .../gradle/cache/internal/SimpleStateCache.java    |   8 +-
 .../btree/BTreePersistentIndexedCache.java         |   2 +-
 .../cache/internal/btree/LockingBlockStore.java    |  14 +-
 .../configuration/DefaultScriptPluginFactory.java  |   4 +-
 .../org/gradle/groovy/scripts/BasicScript.groovy   |  77 --
 .../org/gradle/groovy/scripts/BasicScript.java     |  87 +++
 .../org/gradle/groovy/scripts/DefaultScript.groovy | 175 -----
 .../org/gradle/groovy/scripts/DefaultScript.java   | 187 +++++
 .../scripts/DefaultScriptCompilerFactory.java      |   4 +-
 .../internal/AbstractScriptTransformer.java        |  64 ++
 .../BuildScriptClasspathScriptTransformer.java     |  38 +
 .../scripts/internal/BuildScriptTransformer.java   |  38 +
 .../internal/ClasspathScriptTransformer.java       | 177 +++++
 .../internal/DefaultScriptCompilationHandler.java  |   4 +
 .../scripts/internal/FixMainScriptTransformer.java |  51 ++
 .../internal/StatementLabelsDeprecationLogger.java |  31 +
 .../internal/StatementLabelsScriptTransformer.java |  69 ++
 .../internal/TaskDefinitionScriptTransformer.java  | 194 +++++
 .../gradle/initialization/BuildSourceBuilder.java  |  92 ++-
 .../initialization/DefaultClassLoaderRegistry.java |   5 +-
 .../DefaultCommandLineConverter.java               |  28 +-
 .../DefaultGradleLauncherFactory.java              |   2 +-
 .../gradle/listener/AsyncListenerBroadcast.java    |  38 -
 .../org/gradle/listener/BroadcastDispatch.java     | 136 ++++
 .../gradle/listener/DefaultListenerManager.java    |   1 -
 .../org/gradle/listener/ListenerBroadcast.java     |  25 +-
 .../groovy/org/gradle/logging/ConsoleRenderer.java |  42 ++
 .../org/gradle/logging/LoggingServiceRegistry.java |   8 +-
 .../internal/DefaultProgressLoggerFactory.java     |   2 +-
 .../internal/DefaultStdErrLoggingSystem.java       |   2 +-
 .../internal/DefaultStdOutLoggingSystem.java       |   2 +-
 .../internal/DefaultStyledTextOutputFactory.java   |   2 +-
 .../internal/LoggingBackedStyledTextOutput.java    |   2 +-
 .../org/gradle/logging/internal/MarkerFilter.java  |  67 --
 .../logging/internal/PrintStreamLoggingSystem.java |   2 +-
 .../logging/internal/TerminalDetectorFactory.java  |   2 +-
 .../internal/logback/LogLevelConverter.java        |  70 ++
 .../internal/logback/LogbackLoggingConfigurer.java | 138 ++++
 .../logback/SimpleLogbackLoggingConfigurer.java    |  42 ++
 .../slf4j/SimpleSlf4jLoggingConfigurer.java        |  38 -
 .../internal/slf4j/Slf4jLoggingConfigurer.java     | 188 -----
 .../groovy/org/gradle/messaging/actor/Actor.java   |  61 --
 .../actor/internal/DefaultActorFactory.java        | 161 ----
 .../messaging/concurrent/AsyncStoppable.java       |  37 -
 .../concurrent/DefaultExecutorFactory.java         | 128 ----
 .../messaging/concurrent/ExecutorFactory.java      |  27 -
 .../messaging/concurrent/StoppableExecutor.java    |  35 -
 .../gradle/messaging/dispatch/AsyncDispatch.java   | 193 -----
 .../gradle/messaging/dispatch/AsyncReceive.java    | 204 -----
 .../messaging/dispatch/BroadcastDispatch.java      | 139 ----
 .../gradle/messaging/dispatch/DelayedReceive.java  | 161 ----
 .../org/gradle/messaging/dispatch/Dispatch.java    |  29 -
 .../messaging/dispatch/StoppableDispatch.java      |  25 -
 .../gradle/messaging/remote/ObjectConnection.java  |  63 --
 .../remote/internal/AsyncConnectionAdapter.java    |  84 ---
 .../remote/internal/ConnectException.java          |  24 -
 .../messaging/remote/internal/Connection.java      |  37 -
 .../remote/internal/DefaultIncomingBroadcast.java  |  88 ---
 .../remote/internal/DefaultMessagingServer.java    |  97 ---
 .../internal/DefaultMultiChannelConnector.java     |  74 --
 .../remote/internal/DefaultObjectConnection.java   |  66 --
 .../remote/internal/DefaultOutgoingBroadcast.java  | 116 ---
 .../DisconnectAwareConnectionDecorator.java        | 124 ----
 .../remote/internal/EagerReceiveBuffer.java        | 271 -------
 .../messaging/remote/internal/InputForwarder.java  | 135 ----
 .../gradle/messaging/remote/internal/Message.java  | 163 ----
 .../messaging/remote/internal/MessageHub.java      | 224 ------
 .../remote/internal/MessageIOException.java        |  24 -
 .../remote/internal/MessagingServices.java         | 197 -----
 .../remote/internal/MultiChannelConnection.java    |  46 --
 .../remote/internal/PlaceholderException.java      |  32 -
 .../messaging/remote/internal/ProtocolStack.java   | 323 --------
 .../messaging/remote/internal/ReceiveProtocol.java | 112 ---
 .../messaging/remote/internal/SendProtocol.java    | 116 ---
 .../remote/internal/SynchronizedDispatch.java      |  61 --
 .../remote/internal/inet/InetAddressFactory.java   |  78 --
 .../remote/internal/inet/MulticastConnection.java  |  88 ---
 .../remote/internal/inet/SocketConnection.java     | 233 ------
 .../remote/internal/inet/TcpIncomingConnector.java | 131 ----
 .../remote/internal/inet/TcpOutgoingConnector.java |  78 --
 .../internal/protocol/ConsumerAvailable.java       |  34 -
 .../remote/internal/protocol/ConsumerMessage.java  |  62 --
 .../remote/internal/protocol/ConsumerReady.java    |  22 -
 .../remote/internal/protocol/ConsumerStopped.java  |  22 -
 .../remote/internal/protocol/ConsumerStopping.java |  22 -
 .../internal/protocol/ConsumerUnavailable.java     |  22 -
 .../internal/protocol/ParticipantAvailable.java    |  68 --
 .../internal/protocol/ParticipantUnavailable.java  |  56 --
 .../internal/protocol/ProducerAvailable.java       |  34 -
 .../remote/internal/protocol/ProducerMessage.java  |  62 --
 .../remote/internal/protocol/ProducerReady.java    |  22 -
 .../remote/internal/protocol/ProducerStopped.java  |  22 -
 .../internal/protocol/ProducerUnavailable.java     |  22 -
 .../internal/AbstractExecHandleBuilder.java        |  50 +-
 .../gradle/process/internal/DefaultExecHandle.java | 171 +++--
 .../internal/DefaultProcessForkOptions.java        | 202 ++---
 .../process/internal/DefaultWorkerProcess.java     |   2 +-
 .../internal/DefaultWorkerProcessFactory.java      |  12 +-
 .../org/gradle/process/internal/ExecHandle.java    |   5 +
 .../gradle/process/internal/ExecHandleBuilder.java |  61 +-
 .../gradle/process/internal/ExecHandleRunner.java  |  83 ++-
 .../gradle/process/internal/ExecHandleState.java   |   1 +
 .../process/internal/ExecOutputHandleRunner.java   |  63 --
 .../org/gradle/process/internal/JvmOptions.java    |  16 +-
 .../process/internal/ProcessBuilderFactory.java    |  37 +-
 .../internal/ProcessParentingInitializer.java      |  58 +-
 .../gradle/process/internal/ProcessSettings.java   |  38 +
 ...nClassesInIsolatedClassLoaderWorkerFactory.java |   7 +-
 ...ionClassesInSystemClassLoaderWorkerFactory.java |  16 +-
 .../internal/child/BootstrapSecurityManager.java   |   2 +-
 .../process/internal/child/EncodedStream.java      |  88 +++
 .../child/ImplementationClassLoaderWorker.java     |   1 +
 .../child/SystemApplicationClassLoaderWorker.java  |   2 +-
 .../child/WorkerProcessClassPathProvider.java      |  96 ++-
 .../internal/launcher/GradleWorkerMain.java        |   4 +-
 .../internal/streams/ExecOutputHandleRunner.java   |  73 ++
 .../process/internal/streams/SafeStreams.java      |  41 +
 .../process/internal/streams/StreamsForwarder.java |  78 ++
 .../process/internal/streams/StreamsHandler.java   |  29 +
 .../org/gradle/profile/ProfileEventAdapter.java    |   2 +-
 .../testfixtures/internal/GlobalTestServices.java  |   6 +-
 .../internal/InMemoryCacheFactory.java             |   9 +-
 .../internal/TestOutputEventListener.java          |  36 +
 .../gradle/util/BuildCommencedTimeProvider.java    |   2 +
 .../groovy/org/gradle/util/ClassLoaderFactory.java |   2 +
 .../gradle/util/ClassLoaderObjectInputStream.java  |  43 --
 .../src/main/groovy/org/gradle/util/ClassPath.java |  41 -
 .../src/main/groovy/org/gradle/util/Clock.java     |   3 +
 .../groovy/org/gradle/util/CollectionUtils.java    |  34 +-
 .../org/gradle/util/CompositeIdGenerator.java      |  66 --
 .../main/groovy/org/gradle/util/ConfigureUtil.java |   6 +-
 .../org/gradle/util/DefaultClassLoaderFactory.java |  14 +-
 .../groovy/org/gradle/util/DefaultClassPath.java   |  85 ---
 .../org/gradle/util/DisconnectableInputStream.java |  17 +-
 .../org/gradle/util/FilteringClassLoader.java      |  45 +-
 .../main/groovy/org/gradle/util/GFileUtils.java    | 441 +----------
 .../src/main/groovy/org/gradle/util/GUtil.java     |  26 +-
 .../main/groovy/org/gradle/util/GradleVersion.java |  89 ++-
 .../main/groovy/org/gradle/util/IdGenerator.java   |  29 -
 .../main/groovy/org/gradle/util/JavaMethod.java    |   2 +-
 .../groovy/org/gradle/util/JavaReflectionUtil.java |  40 -
 .../core/src/main/groovy/org/gradle/util/Jvm.java  |  10 +-
 .../groovy/org/gradle/util/LongIdGenerator.java    |  27 -
 .../org/gradle/util/MutableURLClassLoader.java     |   2 +
 .../org/gradle/util/RandomLongIdGenerator.java     |  27 -
 .../groovy/org/gradle/util/ReflectionUtil.groovy   |  19 -
 .../groovy/org/gradle/util/ServiceLocator.java     | 147 ----
 .../src/main/groovy/org/gradle/util/TextUtil.java  |  26 +
 .../main/groovy/org/gradle/util/TimeProvider.java  |  22 -
 .../org/gradle/util/ToStringTransformer.java       |  27 +
 .../groovy/org/gradle/util/TrueTimeProvider.java   |  25 -
 .../main/groovy/org/gradle/util/UUIDGenerator.java |  24 -
 subprojects/core/src/releases.xml                  |  25 -
 .../org/gradle/BuildExceptionReporterTest.groovy   |   6 +-
 .../groovy/org/gradle/BuildResultLoggerTest.java   |   2 +-
 .../groovy/org/gradle/StartParameterTest.groovy    |   1 -
 .../groovy/org/gradle/api/JavaVersionTest.java     | 108 ---
 .../api/internal/AbstractClassGeneratorTest.java   | 825 ---------------------
 .../AbstractNamedDomainObjectContainerTest.groovy  |   2 +
 .../api/internal/AsmBackedClassGeneratorTest.java  | 783 ++++++++++++++++++-
 .../ClassGeneratorBackedInstantiatorTest.groovy    |   1 +
 .../internal/DefaultClassPathRegistryTest.groovy   |   2 +-
 .../DefaultNamedDomainObjectListTest.groovy        |   1 +
 .../internal/DefaultNamedDomainObjectSetTest.java  |   2 +-
 .../DependencyClassPathProviderTest.groovy         |   2 +-
 .../api/internal/DirectInstantiatorTest.groovy     | 197 -----
 .../FactoryNamedDomainObjectContainerSpec.groovy   |   1 +
 ...AutoCreateNamedDomainObjectContainerSpec.groovy |   1 +
 .../org/gradle/api/internal/TestContainer.java     |   2 +-
 .../DefaultArtifactRepositoryContainerTest.groovy  |   2 +-
 .../DefaultConfigurationContainerSpec.groovy       |   2 +-
 .../DefaultConfigurationContainerTest.groovy       |   2 +
 .../dsl/DefaultRepositoryHandlerTest.groovy        |   2 +-
 .../changedetection/CachingHasherTest.java         |   2 +-
 .../DefaultTaskArtifactStateRepositoryTest.java    |   2 +-
 ...hortCircuitTaskArtifactStateRepositoryTest.java |   2 +-
 .../SynchronizedServiceRegistryTest.groovy         |  45 --
 .../internal/file/BaseDirFileResolverSpec.groovy   |   8 +-
 .../internal/file/BaseDirFileResolverTest.groovy   |   8 +-
 .../file/DefaultTemporaryFileProviderTest.groovy   |   3 +-
 .../file/copy/MappingCopySpecVisitorTest.java      |  44 +-
 .../internal/plugins/DefaultConventionTest.groovy  |   2 +-
 .../api/internal/project/DefaultProjectTest.groovy |   1 +
 .../project/GlobalServicesRegistryTest.java        |   2 +-
 .../api/internal/project/ProjectFactoryTest.java   |   3 +-
 .../ProjectInternalServiceRegistryTest.java        |   4 +-
 .../TopLevelBuildServiceRegistryTest.groovy        | 158 ++--
 .../internal/tasks/DefaultTaskContainerTest.java   |   3 +-
 .../util/DefaultProcessForkOptionsTest.groovy      | 222 +++---
 .../plugins/ExtraPropertiesExtensionTest.groovy    |   3 +-
 .../groovy/org/gradle/api/specs/SpecsTest.groovy   |  45 ++
 .../tasks/diagnostics/ProjectReportTaskTest.groovy |   2 +-
 .../internal/AsciiReportRendererTest.groovy        |   2 +-
 .../internal/PropertyReportRendererTest.java       |   2 +-
 .../internal/TaskReportRendererTest.groovy         |   2 +-
 .../internal/TextReportRendererTest.groovy         |   2 +-
 .../cache/internal/DefaultCacheAccessTest.groovy   |  38 +-
 .../cache/internal/DefaultCacheFactoryTest.groovy  |  20 +-
 .../internal/DefaultFileLockManagerTest.groovy     | 276 +++++--
 .../DefaultPersistentDirectoryCacheSpec.groovy     |  47 ++
 .../DefaultPersistentDirectoryCacheTest.java       |  30 +-
 ...gateOnDemandPersistentDirectoryCacheSpec.groovy | 103 +++
 ...ltiProcessSafePersistentIndexedCacheTest.groovy |  16 +-
 .../cache/internal/OnDemandFileAccessTest.groovy   |  78 +-
 .../cache/internal/SimpleStateCacheTest.groovy     |  51 +-
 .../btree/BTreePersistentIndexedCacheTest.java     |   2 +-
 .../DefaultScriptPluginFactoryTest.java            |   2 +-
 .../DefaultScriptCompilationHandlerTest.java       |   1 -
 .../initialization/BuildSourceBuilderTest.groovy   |  70 +-
 .../listener/AsyncListenerBroadcastTest.groovy     | 123 ---
 .../org/gradle/logging/ConsoleRendererTest.groovy  |  38 +
 .../internal/AbstractStyledTextOutputTest.groovy   |  58 +-
 .../org/gradle/logging/internal/ConsoleStub.java   |   2 +
 .../DefaultProgressLoggerFactoryTest.groovy        |   2 +-
 .../LoggingBackedStyledTextOutputTest.groovy       |   2 +-
 .../internal/PrintStreamLoggingSystemTest.groovy   |   2 +-
 .../StyledTextOutputBackedRendererTest.groovy      |   1 +
 .../internal/TerminalDetectorFactoryTest.groovy    |   6 +-
 .../logback/LogbackLoggingConfigurerTest.groovy    | 249 +++++++
 .../slf4j/Slf4jLoggingConfigurerTest.groovy        | 180 -----
 .../concurrent/DefaultExecutorFactoryTest.groovy   | 159 ----
 .../messaging/dispatch/MethodInvocationTest.java   |  35 -
 .../internal/BroadcastSendProtocolTest.groovy      | 135 ----
 .../internal/DefaultObjectConnectionTest.java      | 237 ------
 .../DisconnectAwareConnectionDecoratorTest.groovy  | 168 -----
 .../remote/internal/EagerReceiveBufferTest.groovy  | 135 ----
 .../remote/internal/InputForwarderTest.groovy      | 195 -----
 .../remote/internal/ReceiveProtocolTest.groovy     | 142 ----
 .../remote/internal/SendProtocolTest.groovy        | 191 -----
 .../remote/internal/UnicastSendProtocolTest.groovy | 133 ----
 .../inet/TcpConnectorConcurrencyTest.groovy        |  85 ---
 .../remote/internal/inet/TcpConnectorTest.groovy   |  86 ---
 .../DiscoveryProcotolSerializerTest.groovy         |  96 ---
 .../process/internal/DefaultExecHandleSpec.groovy  | 384 ++++++++++
 .../process/internal/DefaultExecHandleTest.java    | 158 ----
 .../internal/DefaultWorkerProcessFactoryTest.java  |  13 +-
 .../internal/DefaultWorkerProcessTest.groovy       |   2 +-
 .../gradle/process/internal/JvmOptionsTest.groovy  |  50 ++
 .../child/BootstrapSecurityManagerTest.groovy      |   2 +-
 .../internal/child/EncodedStreamTest.groovy        |  70 ++
 .../WorkerProcessClassPathProviderTest.groovy      |  10 +-
 .../src/test/groovy/org/gradle/util/ClockTest.java |   1 +
 .../org/gradle/util/CollectionUtilsTest.groovy     |  67 +-
 .../gradle/util/CompositeIdGeneratorTest.groovy    |  80 --
 .../org/gradle/util/DefaultClassPathTest.groovy    |  58 --
 .../gradle/util/FilteringClassLoaderTest.groovy    |  52 +-
 .../test/groovy/org/gradle/util/GUtilTest.groovy   |  19 +
 .../org/gradle/util/GradleVersionTest.groovy       | 128 +++-
 .../org/gradle/util/LongIdGeneratorTest.groovy     |  49 --
 .../org/gradle/util/ServiceLocatorTest.groovy      | 183 -----
 .../groovy/org/gradle/util/TextUtilTest.groovy     |  15 +
 .../gradle/api/tasks/AbstractSpockTaskTest.groovy  |   7 +-
 .../DefaultFileLockManagerTestHelper.groovy        |  68 ++
 .../org/gradle/logging/TestStyledTextOutput.groovy |  80 ++
 .../logging/TestStyledTextOutputFactory.java       |  53 ++
 .../tests/fixtures/ConcurrentTestUtil.groovy       |   4 +-
 .../org/gradle/util/MultithreadedTestCase.java     |   4 +-
 .../org/gradle/plugins/cpp/AvailableCompilers.java |  13 +-
 .../plugins/cpp/CppIntegrationTestRunner.java      |   7 +-
 .../gradle/plugins/binaries/BinariesPlugin.java    |   2 +-
 .../model/internal/DefaultCompilerRegistry.java    |   2 +-
 .../org/gradle/plugins/cpp/CppExtension.java       |   2 +-
 .../cpp/compiler/internal/ArgCollector.java        |  23 -
 .../plugins/cpp/compiler/internal/ArgWriter.java   |  82 --
 .../CommandLinCppCompilerArgumentsApplicator.java  |  36 -
 .../compiler/internal/CommandLineCppCompiler.java  |   9 +-
 ...ommandLineCppCompilerArgumentsToOptionFile.java |  10 +-
 .../compiler/internal/CompileSpecToArguments.java  |  25 -
 .../cpp/compiler/internal/ListArgCollector.java    |  52 --
 .../gpp/internal/GppCompileSpecToArguments.java    |   4 +-
 .../plugins/cpp/gpp/internal/GppCompiler.java      |   7 +-
 .../cpp/gpp/internal/GppCompilerAdapter.java       |   2 +-
 .../internal/VisualCppCompileSpecToArguments.java  |   4 +-
 .../cpp/msvcpp/internal/VisualCppCompiler.java     |   2 +-
 .../internal/DefaultCompilerRegistryTest.groovy    |   2 +-
 .../cpp/compiler/internal/ArgWriterSpec.groovy     |  76 --
 subprojects/docs/docs.gradle                       |  32 +-
 subprojects/docs/release-notes-transform.gradle    |  48 +-
 subprojects/docs/src/docs/css/dsl.css              |   4 +
 subprojects/docs/src/docs/dsl/dsl.xml              |  34 +
 .../org.gradle.api.plugins.quality.FindBugs.xml    |   2 +-
 ...rg.gradle.api.tasks.compile.AbstractCompile.xml |   2 +-
 .../org.gradle.api.tasks.compile.GroovyCompile.xml |   2 +-
 ....tasks.testing.logging.TestLoggingContainer.xml |  87 +++
 ...g.gradle.plugins.ide.idea.model.IdeaProject.xml |   4 +-
 .../docs/src/docs/release/content/style.css        |  50 +-
 subprojects/docs/src/docs/release/notes.md         | 370 ++++-----
 .../docs/src/docs/userguide/announcePlugin.xml     |  97 +--
 .../src/docs/userguide/buildScriptsTutorial.xml    |   4 +-
 .../src/docs/userguide/commandLineTutorial.xml     |   7 +-
 subprojects/docs/src/docs/userguide/depMngmt.xml   |   3 +
 subprojects/docs/src/docs/userguide/earPlugin.xml  |   4 +-
 subprojects/docs/src/docs/userguide/embedding.xml  |   4 +-
 .../docs/src/docs/userguide/groovyTutorial.xml     |   6 +-
 subprojects/docs/src/docs/userguide/javaPlugin.xml |   5 +
 .../docs/src/docs/userguide/mavenPlugin.xml        |  63 +-
 .../docs/src/docs/userguide/standardPlugins.xml    |   2 +-
 subprojects/docs/src/docs/userguide/userguide.xml  |  10 +-
 .../docs/src/docs/userguide/workingWithFiles.xml   |   2 +-
 .../src/docs/userguide/writingBuildScripts.xml     |  17 +-
 subprojects/docs/src/samples/announce/build.gradle |   6 +-
 .../samples/customDistribution/plugin/build.gradle |   2 +-
 .../src/samples/customPlugin/consumer/build.gradle |   3 -
 .../docs/src/samples/ivypublish/build.gradle       |   2 +-
 subprojects/docs/src/samples/osgi/build.gradle     |   2 +-
 .../docs/src/samples/toolingApi/build/build.gradle |  25 +-
 .../docs/src/samples/toolingApi/build/readme.xml   |   2 +-
 .../src/samples/toolingApi/eclipse/build.gradle    |  25 +-
 .../docs/src/samples/toolingApi/eclipse/readme.xml |   2 +-
 .../docs/src/samples/toolingApi/idea/build.gradle  |  32 +-
 .../docs/src/samples/toolingApi/idea/readme.xml    |  18 +-
 .../docs/src/samples/toolingApi/model/build.gradle |  19 +
 .../docs/src/samples/toolingApi/model/readme.xml   |   3 +
 .../src/main/java/org/gradle/sample/Main.java      |  33 +
 .../artifacts/externalDependencies/build.gradle    |  16 +-
 .../samples/userguide/artifacts/maven/build.gradle |   8 +-
 .../userguide/organizeBuildLogic/build.gradle      |   2 +-
 .../tutorial/extraProperties/build.gradle          |  28 +-
 .../samples/userguideOutput/extraProperties.out    |   4 +
 .../userguideOutput/extraTaskProperties.out        |   2 +-
 .../groovy/org/gradle/plugins/ear/EarPlugin.java   |   2 +-
 .../eclipse/EclipseClasspathIntegrationTest.groovy |   2 +-
 ...ClasspathRemoteResolutionIntegrationTest.groovy |   4 -
 .../plugins/ide/idea/IdeaIntegrationTest.groovy    |   5 +-
 .../ide/idea/IdeaModuleIntegrationTest.groovy      |  81 +-
 .../ide/idea/IdeaProjectIntegrationTest.groovy     |   3 +-
 .../canCreateAndDeleteMetaData/build.gradle        |   2 +
 .../build.gradle                                   |   1 +
 .../worksWithAnEmptyProject/build.gradle           |   1 +
 .../expectedFiles/root/root.ipr.xml                |   2 +-
 .../worksWithNonStandardLayout/root/build.gradle   |   2 +
 .../plugins/ide/eclipse/EclipsePlugin.groovy       |   2 +-
 .../plugins/ide/eclipse/EclipseWtpPlugin.groovy    |   2 +-
 .../plugins/ide/eclipse/GenerateEclipseJdt.groovy  |   2 +-
 .../ide/eclipse/GenerateEclipseProject.groovy      |   2 +-
 .../ide/eclipse/GenerateEclipseWtpComponent.groovy |   2 +-
 .../ide/eclipse/GenerateEclipseWtpFacet.groovy     |   2 +-
 .../ide/eclipse/model/AbstractLibrary.groovy       |   5 +
 .../eclipse/model/internal/ClasspathFactory.groovy |  21 +-
 .../org/gradle/plugins/ide/idea/IdeaPlugin.groovy  |   5 +-
 .../ide/idea/model/SingleEntryModuleLibrary.groovy |  11 +-
 .../model/internal/IdeaDependenciesProvider.groovy |   6 +-
 .../ide/internal/IdeDependenciesExtractor.groovy   |  46 +-
 .../internal/provider/BuildModelAction.java        |   3 +-
 .../internal/provider/EclipseModelBuilder.java     |   3 +-
 .../internal/provider/IdeaModelBuilder.java        |   7 +-
 .../internal/provider/MigrationModelBuilder.java   |  74 ++
 .../gradle/plugins/ide/idea/IdeaPluginTest.groovy  |   4 +-
 .../internal/IdeDependenciesExtractorTest.groovy   |  48 --
 .../integtests/ArchiveIntegrationTest.groovy       | 799 --------------------
 .../BuildSourceBuilderIntegrationTest.groovy       |  74 ++
 .../integtests/CacheProjectIntegrationTest.groovy  |  35 +-
 .../integtests/CommandLineIntegrationTest.groovy   |   9 +-
 .../integtests/DistributionIntegrationTest.groovy  |  11 +-
 .../integtests/DynamicObjectIntegrationTest.groovy | 454 ------------
 .../integtests/GroovyProjectIntegrationTest.groovy |   2 +-
 ...crementalJavaProjectBuildIntegrationTest.groovy |   1 +
 .../IncrementalTestIntegrationTest.groovy          |   6 +
 .../integtests/LoggingIntegrationTest.groovy       | 470 ------------
 .../OsgiProjectSampleIntegrationTest.groovy        |   2 +-
 .../integtests/WorkerProcessIntegrationTest.java   |  25 +-
 .../SingleUseDaemonIntegrationTest.groovy          | 124 ----
 .../integtests/fixture/M2Installation.groovy       |  44 ++
 .../logging/LoggerIsEnabledIntegrationTest.groovy  |  37 +
 .../logging/LoggingIntegrationTest.groovy          | 469 ++++++++++++
 .../ivy/IvyHttpPublishIntegrationTest.groovy       | 320 ++++++++
 .../ivy/IvyLocalPublishIntegrationTest.groovy      |  85 +++
 .../publish/ivy/IvyPublishIntegrationTest.groovy   | 214 ------
 .../IvySingleProjectPublishIntegrationTest.groovy  | 129 ++++
 .../MavenMultiProjectPublishIntegrationTest.groovy | 146 ++++
 .../maven/MavenPomGenerationIntegrationTest.groovy | 109 +++
 .../maven/MavenPublicationIntegrationTest.groovy   |  80 --
 .../maven/MavenPublishIntegrationTest.groovy       | 326 ++++++++
 .../AbstractDependencyResolutionTest.groovy        |   4 -
 .../ArtifactDependenciesIntegrationTest.groovy     |   8 +-
 .../ArtifactOnlyResolutionIntegrationTest.groovy   |   4 -
 ...CacheDependencyResolutionIntegrationTest.groovy | 116 ---
 .../resolve/CacheResolveIntegrationTest.groovy     | 167 +++++
 ...odingDependencyResolutionIntegrationTest.groovy |  47 --
 ...ProxyDependencyResolutionIntegrationTest.groovy | 146 ----
 ...irectDependencyResolutionIntegrationTest.groovy |  82 --
 .../ResolvedConfigurationIntegrationTest.groovy    |  10 +-
 .../CacheReuseCrossVersionIntegrationTest.groovy   |  17 +-
 .../M3CacheReuseCrossVersionIntegrationTest.groovy |   5 -
 .../MavenLocalCacheReuseIntegrationTest.groovy     |  87 ---
 .../MavenM2CacheReuseIntegrationTest.groovy        |  64 ++
 ...ationDependencyResolutionIntegrationTest.groovy | 221 ++++++
 ...odingDependencyResolutionIntegrationTest.groovy |  49 ++
 .../http/HttpProxyResolveIntegrationTest.groovy    | 154 ++++
 .../http/HttpRedirectResolveIntegrationTest.groovy |  87 +++
 ...emoteDependencyResolutionIntegrationTest.groovy | 194 -----
 .../IvyBrokenRemoteResolveIntegrationTest.groovy   | 194 +++++
 ...ingModuleRemoteResolutionIntegrationTest.groovy | 425 -----------
 ...angingModuleRemoteResolveIntegrationTest.groovy | 423 +++++++++++
 .../ivy/IvyDependencyResolveIntegrationTest.groovy | 144 ----
 ...cRevisionRemoteResolutionIntegrationTest.groovy | 455 ------------
 ...amicRevisionRemoteResolveIntegrationTest.groovy | 457 ++++++++++++
 .../ivy/IvyFileRepoResolveIntegrationTest.groovy   | 125 ++++
 .../ivy/IvyHttpRepoResolveIntegrationTest.groovy   | 213 ++++++
 ...LocalDependencyResolutionIntegrationTest.groovy | 125 ----
 ...emoteDependencyResolutionIntegrationTest.groovy | 256 -------
 .../resolve/ivy/IvyResolveIntegrationTest.groovy   | 144 ++++
 .../BadPomFileDependenciesIntegrationTest.groovy   |  46 --
 .../maven/BadPomFileResolveIntegrationTest.groovy  |  46 ++
 .../MavenDynamicResolveIntegrationTest.groovy      |  71 ++
 .../MavenFileRepoResolveIntegrationTest.groovy     | 137 ++++
 .../MavenHttpRepoResolveIntegrationTest.groovy     | 330 +++++++++
 ...LocalDependencyResolutionIntegrationTest.groovy | 137 ----
 .../MavenLocalRepoResolveIntegrationTest.groovy    | 196 +++++
 .../MavenParentPomResolveIntegrationTest.groovy    | 124 ++++
 .../MavenPomPackagingResolveIntegrationTest.groovy | 247 ++++++
 ...emoteDependencyResolutionIntegrationTest.groovy | 319 --------
 .../MavenRemotePomResolutionIntegrationTest.groovy | 179 -----
 ...emoteDependencyResolutionIntegrationTest.groovy | 589 ---------------
 .../MavenSnapshotResolveIntegrationTest.groovy     | 592 +++++++++++++++
 .../samples/SamplesAnnounceIntegrationTest.groovy  |  56 ++
 .../shared/build.gradle                            |  20 +
 .../LoggingIntegrationTest/deprecated/build.gradle |   0
 .../LoggingIntegrationTest/logging/build.gradle    |   0
 .../logging/buildSrc/build.gradle                  |   0
 .../LoggingIntegrationTest/logging/external.gradle |   0
 .../LoggingIntegrationTest/logging/init.gradle     |   0
 .../logging/nestedBuild/build.gradle               |   0
 .../logging/nestedBuild/buildSrc/build.gradle      |   0
 .../logging/nestedBuild/settings.gradle            |   0
 .../logging/project1/build.gradle                  |   0
 .../logging/project2/build.gradle                  |   0
 .../LoggingIntegrationTest/logging/settings.gradle |   0
 .../multiThreaded/build.gradle                     |   0
 .../build.gradle                                   |  17 -
 .../settings.gradle                                |   1 -
 .../build.gradle                                   |  38 -
 .../settings.gradle                                |   1 -
 .../build.gradle                                   |  19 -
 .../settings.gradle                                |   1 -
 .../build.gradle                                   |   0
 .../shared/producer.gradle                         |   0
 .../shared/projectWithMavenSnapshots.gradle        |   0
 .../shared/src/main/java/org/gradle/Test.java      |   0
 .../internal-integ-testing.gradle                  |  49 +-
 .../fixtures/AbstractIntegrationSpec.groovy        |  13 +
 .../integtests/fixtures/AvailableJavaHomes.java    |  75 +-
 .../integtests/fixtures/DaemonGradleExecuter.java  |   2 +-
 .../fixtures/EmbeddedDaemonGradleExecuter.java     |   3 +-
 .../integtests/fixtures/ForkingGradleHandle.java   |   5 +-
 .../integtests/fixtures/GradleDistribution.java    |   7 +-
 .../fixtures/GradleDistributionExecuter.java       |  25 +-
 .../gradle/integtests/fixtures/HttpServer.groovy   | 332 +++++++--
 .../fixtures/InProcessGradleExecuter.java          |   9 +-
 .../integtests/fixtures/IvyRepository.groovy       | 125 ++--
 .../integtests/fixtures/MavenRepository.groovy     | 132 +++-
 .../fixtures/MultiVersionIntegrationSpec.groovy    |   6 +-
 .../fixtures/PreviousGradleVersionExecuter.groovy  |   6 +-
 .../integtests/fixtures/ScriptExecuter.groovy      |   9 +-
 .../gradle/integtests/fixtures/TestResources.java  |   5 +
 .../fixtures/UnexpectedBuildFailure.java           |  27 +
 .../fixtures/WellBehavedPluginTest.groovy          |   8 +-
 .../internal-testing/internal-testing.gradle       |   3 +-
 .../testing/internal/util/GradlewRunner.java       |  80 ++
 .../testing/internal/util/IdeQuickCheckRunner.java |  68 --
 .../groovy/org/gradle/util/DynamicDelegate.groovy  |  46 ++
 .../groovy/org/gradle/util/TestDirHelper.groovy    |   4 +
 .../src/main/groovy/org/gradle/util/TestFile.java  |  44 +-
 .../groovy/org/gradle/util/TestFileHelper.groovy   |  59 +-
 .../groovy/org/gradle/util/TestPrecondition.groovy |   3 +
 subprojects/javascript/javascript.gradle           |  29 +
 .../JavaScriptBasePluginIntegrationTest.groovy     |  62 ++
 .../CoffeeScriptBasePluginIntegrationTest.groovy   |  90 +++
 .../envjs/EnvJsPluginIntegrationTest.groovy        | 100 +++
 .../jshint/JsHintPluginIntegrationTest.groovy      | 105 +++
 .../rhino/RhinoPluginIntegrationTest.groovy        | 106 +++
 .../javascript/base/JavaScriptBasePlugin.groovy    |  35 +
 .../javascript/base/JavaScriptExtension.java       |  60 ++
 .../coffeescript/CoffeeScriptBasePlugin.groovy     |  69 ++
 .../coffeescript/CoffeeScriptCompile.java          | 100 +++
 .../coffeescript/CoffeeScriptCompileOptions.java   |  32 +
 .../coffeescript/CoffeeScriptCompileSpec.java      |  41 +
 .../coffeescript/CoffeeScriptCompiler.java         |  25 +
 .../coffeescript/CoffeeScriptExtension.java        |  49 ++
 .../CoffeeScriptCompileDestinationCalculator.java  |  52 ++
 .../internal/CoffeeScriptCompileResult.java        |  32 +
 .../internal/DefaultCoffeeScriptCompileSpec.java   |  63 ++
 .../SerializableCoffeeScriptCompileSpec.java       |  78 ++
 .../internal/rhino/CoffeeScriptCompilerWorker.java |  65 ++
 .../internal/rhino/RhinoCoffeeScriptCompiler.java  |  68 ++
 .../plugins/javascript/envjs/EnvJsExtension.java   |  50 ++
 .../plugins/javascript/envjs/EnvJsPlugin.groovy    |  95 +++
 .../javascript/envjs/browser/BrowserEvaluate.java  |  98 +++
 .../javascript/envjs/browser/BrowserEvaluator.java |  25 +
 .../javascript/envjs/http/HttpFileServer.java      |  28 +
 .../envjs/http/HttpFileServerFactory.java          |  25 +
 .../envjs/http/simple/SimpleHttpFileServer.java    |  51 ++
 .../http/simple/SimpleHttpFileServerFactory.java   |  61 ++
 .../simple/internal/SimpleFileServerContainer.java |  75 ++
 .../envjs/internal/EnvJsBrowserEvaluator.java      |  72 ++
 .../envjs/internal/EnvJsEvaluateSpec.java          |  39 +
 .../envjs/internal/EnvJsEvaluateWorker.java        |  58 ++
 .../gradle/plugins/javascript/jshint/JsHint.java   | 162 ++++
 .../plugins/javascript/jshint/JsHintExtension.java |  49 ++
 .../plugins/javascript/jshint/JsHintPlugin.groovy  |  74 ++
 .../javascript/jshint/internal/JsHintResult.java   |  34 +
 .../javascript/jshint/internal/JsHintSpec.java     |  52 ++
 .../javascript/jshint/internal/JsHintWorker.java   |  69 ++
 .../plugins/javascript/rhino/RhinoExtension.java   |  58 ++
 .../plugins/javascript/rhino/RhinoPlugin.groovy    |  71 ++
 .../plugins/javascript/rhino/RhinoShellExec.java   | 112 +++
 .../javascript/rhino/worker/RhinoWorker.java       |  29 +
 .../javascript/rhino/worker/RhinoWorkerHandle.java |  25 +
 .../rhino/worker/RhinoWorkerHandleFactory.java     |  31 +
 .../javascript/rhino/worker/RhinoWorkerSpec.java   |  45 ++
 .../javascript/rhino/worker/RhinoWorkerUtils.java  | 140 ++++
 .../worker/internal/DefaultRhinoWorkerHandle.java  |  95 +++
 .../internal/DefaultRhinoWorkerHandleFactory.java  |  56 ++
 .../worker/internal/RhinoClientWorkerProtocol.java |  25 +
 .../rhino/worker/internal/RhinoServer.java         |  60 ++
 .../worker/internal/RhinoWorkerClientProtocol.java |  29 +
 .../rhino/worker/internal/RhinoWorkerReceiver.java |  68 ++
 .../gradle-plugins/coffeescript-base.properties    |   2 +
 .../META-INF/gradle-plugins/envjs.properties       |   1 +
 .../gradle-plugins/javascript-base.properties      |   1 +
 .../META-INF/gradle-plugins/jshint.properties      |   1 +
 .../META-INF/gradle-plugins/rhino.properties       |   1 +
 .../base/JavaScriptBasePluginTest.groovy           |  58 ++
 .../coffeescript/CoffeeScriptBasePluginTest.groovy |  41 +
 .../simple/SimpleHttpFileServerFactoryTest.groovy  |  77 ++
 .../javascript/rhino/RhinoPluginTest.groovy        |  45 ++
 .../base/JavaScriptBasePluginTestFixtures.groovy   |  45 ++
 .../CoffeeScriptBasePluginTestFixtures.groovy      |  31 +
 .../api/plugins/jetty/AbstractJettyRunTask.java    |   4 +-
 subprojects/launcher/launcher.gradle               |  11 +-
 .../DaemonConfigurabilityIntegrationSpec.groovy    |  25 +
 .../daemon/SingleUseDaemonIntegrationTest.groovy   | 124 ++++
 .../StoppingDaemonSmokeIntegrationSpec.groovy      |  13 +
 .../daemon/testing/DaemonsEventSequence.groovy     |   4 +-
 .../main/java/org/gradle/launcher/GradleMain.java  |  52 +-
 .../src/main/java/org/gradle/launcher/Main.java    |   4 +-
 .../java/org/gradle/launcher/ProcessBootstrap.java |  52 --
 .../org/gradle/launcher/bootstrap/EntryPoint.java  |  82 ++
 .../launcher/bootstrap/ExecutionCompleter.java     |  21 +
 .../launcher/bootstrap/ExecutionListener.java      |  32 +
 .../launcher/bootstrap/ProcessBootstrap.java       |  52 ++
 .../launcher/bootstrap/ProcessCompleter.java       |  26 +
 .../org/gradle/launcher/cli/ActionAdapter.java     |   2 +-
 .../gradle/launcher/cli/BuildActionsFactory.java   |  18 +-
 .../org/gradle/launcher/cli/CommandLineAction.java |   2 +-
 .../launcher/cli/CommandLineActionFactory.java     |   3 +-
 .../org/gradle/launcher/cli/DaemonBuildAction.java |  52 --
 .../launcher/cli/ExceptionReportingAction.java     |  41 +
 .../org/gradle/launcher/cli/GuiActionsFactory.java |   2 +-
 .../org/gradle/launcher/cli/RunBuildAction.java    |  54 +-
 .../launcher/daemon/DaemonExecHandleBuilder.java   |  43 ++
 .../launcher/daemon/bootstrap/DaemonGreeter.java   |  64 ++
 .../launcher/daemon/bootstrap/DaemonMain.java      |  34 +-
 .../daemon/bootstrap/DaemonOutputConsumer.java     |  96 +++
 .../bootstrap/DaemonStartupCommunication.java      |  67 ++
 .../daemon/bootstrap/ForegroundDaemonMain.java     |   5 +
 .../launcher/daemon/bootstrap/GradleDaemon.java    |   2 +-
 .../launcher/daemon/client/DaemonClient.java       |  42 +-
 .../daemon/client/DaemonClientInputForwarder.java  |  27 +-
 .../daemon/client/DaemonClientServices.java        |   2 +-
 .../daemon/client/DaemonClientServicesSupport.java |  24 +-
 .../launcher/daemon/client/DaemonConnection.java   |  12 +-
 .../launcher/daemon/client/DaemonConnector.java    |   8 +-
 .../launcher/daemon/client/DaemonStarter.java      |   4 +-
 .../daemon/client/DefaultDaemonConnector.java      |  48 +-
 .../daemon/client/DefaultDaemonStarter.java        |  37 +-
 .../client/EmbeddedDaemonClientServices.java       |   9 +-
 .../daemon/client/EmbeddedDaemonStarter.java       |  48 +-
 .../launcher/daemon/client/InputForwarder.java     | 135 ++++
 .../daemon/client/SingleUseDaemonClient.java       |  21 +-
 .../client/SingleUseDaemonClientServices.java      |  19 +-
 .../daemon/client/StopDaemonClientServices.java    |  17 +-
 .../launcher/daemon/client/StopDispatcher.java     |  12 +-
 .../daemon/context/DaemonCompatibilitySpec.java    |  31 +-
 .../daemon/diagnostics/DaemonDiagnostics.java      |  35 +
 .../daemon/diagnostics/DaemonStartupInfo.java      |  59 ++
 .../launcher/daemon/logging/DaemonGreeter.java     |  76 --
 .../launcher/daemon/logging/DaemonMessages.java    |   1 +
 .../org/gradle/launcher/daemon/protocol/Build.java |  38 +-
 .../launcher/daemon/protocol/BuildAndStop.java     |   4 +-
 .../launcher/daemon/protocol/CloseInput.java       |   8 +-
 .../gradle/launcher/daemon/protocol/Command.java   |  21 +-
 .../launcher/daemon/protocol/ForwardInput.java     |   7 +-
 .../gradle/launcher/daemon/protocol/IoCommand.java |   8 +-
 .../org/gradle/launcher/daemon/protocol/Stop.java  |   6 +-
 .../daemon/registry/EmbeddedDaemonRegistry.java    |  50 +-
 .../daemon/registry/PersistentDaemonRegistry.java  |  25 +-
 .../org/gradle/launcher/daemon/server/Daemon.java  |   4 +-
 .../launcher/daemon/server/DaemonServices.java     |   4 +-
 .../daemon/server/DaemonStateCoordinator.java      |   7 +-
 .../daemon/server/DaemonTcpServerConnector.java    |  15 +-
 .../server/SynchronizedDispatchConnection.java     |  61 ++
 .../daemon/server/exec/DaemonCommandExecuter.java  |   5 +-
 .../daemon/server/exec/DaemonCommandExecution.java |  11 +-
 .../daemon/server/exec/DaemonStateControl.java     |  54 ++
 .../server/exec/DefaultDaemonCommandExecuter.java  |   7 +-
 .../launcher/daemon/server/exec/ExecuteBuild.java  |  10 +-
 .../daemon/server/exec/ForwardClientInput.java     |   4 +-
 .../launcher/daemon/server/exec/HandleStop.java    |   2 +-
 .../launcher/daemon/server/exec/LogToClient.java   |   2 +-
 .../server/exec/StartBuildOrRespondWithBusy.java   |   3 +-
 .../server/exec/StartStopIfBuildAndStop.java       |   2 +-
 .../daemon/server/exec/WatchForDisconnection.java  |   2 +-
 .../launcher/exec/BuildActionParameters.java       |   6 +-
 .../exec/DefaultBuildActionParameters.java         |  13 +-
 .../java/org/gradle/launcher/exec/EntryPoint.java  |  82 --
 .../launcher/exec/ExceptionReportingAction.java    |  39 -
 .../gradle/launcher/exec/ExecutionCompleter.java   |  21 -
 .../gradle/launcher/exec/ExecutionListener.java    |  32 -
 .../InProcessGradleLauncherActionExecuter.java     |  46 ++
 .../org/gradle/launcher/exec/ProcessCompleter.java |  26 -
 .../DaemonGradleLauncherActionExecuter.java        |  11 +-
 .../internal/provider/DefaultConnection.java       |  27 +-
 .../internal/provider/EmbeddedExecuterSupport.java |  15 +-
 .../EmbeddedGradleLauncherActionExecuter.java      |  53 --
 .../groovy/org/gradle/launcher/MainTest.groovy     |   4 +-
 .../launcher/bootstrap/EntryPointTest.groovy       |  70 ++
 .../launcher/cli/BuildActionsFactoryTest.groovy    |  21 +-
 .../cli/CommandLineActionFactoryTest.groovy        |   2 +-
 .../launcher/cli/DaemonBuildActionTest.groovy      |  49 --
 .../cli/ExceptionReportingActionTest.groovy        |  63 ++
 .../gradle/launcher/cli/RunBuildActionTest.groovy  |  66 +-
 .../daemon/DaemonExecHandleBuilderSpec.groovy      |  42 ++
 .../launcher/daemon/EmbeddedDaemonSmokeTest.groovy |   3 +-
 .../daemon/bootstrap/DaemonGreeterTest.groovy      |  70 ++
 .../bootstrap/DaemonOutputConsumerTest.groovy      | 102 +++
 .../DaemonStartupCommunicationSpec.groovy          |  64 ++
 .../client/DaemonClientInputForwarderTest.groovy   |  10 +-
 .../launcher/daemon/client/DaemonClientTest.groovy |  15 +-
 .../client/DefaultDaemonConnectorTest.groovy       |  34 +-
 .../daemon/client/InputForwarderTest.groovy        | 195 +++++
 .../daemon/client/StopDispatcherTest.groovy        |  54 ++
 .../context/DaemonCompatibilitySpecSpec.groovy     |  28 +-
 .../diagnostics/DaemonDiagnosticsTest.groovy       |  55 ++
 .../registry/DaemonRegistryServicesTest.groovy     |  19 +
 .../registry/PersistentDaemonRegistryTest.groovy   |  76 ++
 .../DaemonServerExceptionHandlingTest.groovy       |   5 +-
 .../daemon/server/StopDispatcherTest.groovy        |  56 --
 .../exec/DefaultBuildActionParametersTest.groovy   |   3 +-
 .../org/gradle/launcher/exec/EntryPointTest.groovy |  70 --
 .../exec/ExceptionReportingActionTest.groovy       |  61 --
 ...nProcessGradleLauncherActionExecuterTest.groovy | 100 +++
 ...EmbeddedGradleLauncherActionExecuterTest.groovy |  80 --
 ...BridgingGradleLauncherActionExecuterTest.groovy |   2 +-
 .../maven/internal/ant/AbstractMavenResolver.java  |   1 +
 .../maven/internal/ant/CustomDeployTask.java       |   9 +-
 .../ant/CustomInstallDeployTaskSupport.java        |   1 +
 .../maven/internal/ant/CustomInstallTask.java      |   4 +
 .../ant/DefaultPomDependenciesConverter.java       |  12 +-
 .../ProjectDependencyArtifactIdExtractorHack.java  | 108 +++
 .../modelbuilder/MavenPublicationBuilder.groovy    |   2 +-
 .../internal/ant/AbstractMavenResolverTest.java    |   1 +
 .../internal/ant/DefaultMavenPublisherTest.groovy  |   4 +-
 ...ectDependencyArtifactIdExtractorHackTest.groovy |  91 +++
 subprojects/messaging/messaging.gradle             |   8 +
 .../java/org/gradle/messaging/actor/Actor.java     |  62 ++
 .../org/gradle/messaging/actor/ActorFactory.java   |   0
 .../actor/internal/DefaultActorFactory.java        | 161 ++++
 .../gradle/messaging/dispatch/AsyncDispatch.java   | 193 +++++
 .../gradle/messaging/dispatch/AsyncReceive.java    | 204 +++++
 .../dispatch/ContextClassLoaderDispatch.java       |   0
 .../gradle/messaging/dispatch/DelayedReceive.java  | 161 ++++
 .../dispatch/DiscardingFailureHandler.java         |   0
 .../org/gradle/messaging/dispatch/Dispatch.java    |  29 +
 .../messaging/dispatch/DispatchException.java      |   0
 .../messaging/dispatch/DispatchFailureHandler.java |   0
 .../dispatch/ExceptionTrackingFailureHandler.java  |   0
 .../dispatch/FailureHandlingDispatch.java          |   0
 .../messaging/dispatch/MethodInvocation.java       |   0
 .../messaging/dispatch/ProxyDispatchAdapter.java   |   0
 .../gradle/messaging/dispatch/QueuingDispatch.java |   0
 .../org/gradle/messaging/dispatch/Receive.java     |   0
 .../messaging/dispatch/ReflectionDispatch.java     |   0
 .../java}/org/gradle/messaging/remote/Address.java |   0
 .../org/gradle/messaging/remote/Addressable.java   |   0
 .../org/gradle/messaging/remote/ConnectEvent.java  |   0
 .../gradle/messaging/remote/MessagingClient.java   |   0
 .../gradle/messaging/remote/MessagingServer.java   |   0
 .../gradle/messaging/remote/ObjectConnection.java  |  63 ++
 .../messaging/remote/internal/AsyncConnection.java |   0
 .../remote/internal/AsyncConnectionAdapter.java    |  84 +++
 .../remote/internal/BroadcastSendProtocol.java     |   0
 .../remote/internal/BufferingProtocol.java         |   0
 .../remote/internal/ChannelLookupProtocol.java     |   0
 .../internal/ChannelRegistrationProtocol.java      |   0
 .../remote/internal/CompositeAddress.java          |   0
 .../remote/internal/ConnectException.java          |  22 +
 .../messaging/remote/internal/Connection.java      |  37 +
 .../remote/internal/DefaultIncomingBroadcast.java  |  89 +++
 .../remote/internal/DefaultMessageSerializer.java  |   0
 .../remote/internal/DefaultMessagingClient.java    |   0
 .../remote/internal/DefaultMessagingServer.java    |  97 +++
 .../internal/DefaultMultiChannelConnection.java    |   0
 .../internal/DefaultMultiChannelConnector.java     |  74 ++
 .../remote/internal/DefaultObjectConnection.java   |  66 ++
 .../remote/internal/DefaultOutgoingBroadcast.java  | 117 +++
 .../remote/internal/DelegatingConnection.java      |   0
 .../remote/internal/DisconnectAwareConnection.java |   0
 .../DisconnectAwareConnectionDecorator.java        | 123 +++
 .../remote/internal/EagerReceiveBuffer.java        | 267 +++++++
 .../remote/internal/GroupMessageFilter.java        |   0
 .../internal/HandshakeIncomingConnector.java       |   0
 .../internal/HandshakeOutgoingConnector.java       |   0
 .../remote/internal/IncomingBroadcast.java         |   0
 .../remote/internal/IncomingConnector.java         |   0
 .../internal/IncomingMethodInvocationHandler.java  |   0
 .../gradle/messaging/remote/internal/Message.java  | 163 ++++
 .../messaging/remote/internal/MessageHub.java      | 221 ++++++
 .../remote/internal/MessageIOException.java        |  22 +
 .../remote/internal/MessageOriginator.java         |   0
 .../remote/internal/MessageSerializer.java         |   0
 .../remote/internal/MessagingServices.java         | 194 +++++
 .../MethodInvocationMarshallingDispatch.java       |   0
 .../MethodInvocationUnmarshallingDispatch.java     |   0
 .../remote/internal/MultiChannelConnection.java    |  46 ++
 .../remote/internal/MultiChannelConnector.java     |   0
 .../remote/internal/OutgoingBroadcast.java         |   0
 .../remote/internal/OutgoingConnector.java         |   0
 .../internal/OutgoingMethodInvocationHandler.java  |   0
 .../remote/internal/OutgoingMultiplex.java         |   0
 .../remote/internal/PlaceholderException.java      |  36 +
 .../gradle/messaging/remote/internal/Protocol.java |   0
 .../messaging/remote/internal/ProtocolContext.java |   0
 .../messaging/remote/internal/ProtocolStack.java   | 323 ++++++++
 .../messaging/remote/internal/ReceiveProtocol.java | 113 +++
 .../remote/internal/RemoteDisconnectProtocol.java  |   0
 .../gradle/messaging/remote/internal/Router.java   |   0
 .../messaging/remote/internal/SendProtocol.java    | 113 +++
 .../remote/internal/TypeCastDispatch.java          |   0
 .../remote/internal/UnicastSendProtocol.java       |   0
 .../messaging/remote/internal/WorkerProtocol.java  |   0
 .../remote/internal/inet/InetAddressFactory.java   | 169 +++++
 .../remote/internal/inet/InetEndpoint.java         |   0
 .../remote/internal/inet/MultiChoiceAddress.java   |   0
 .../remote/internal/inet/MulticastConnection.java  |  88 +++
 .../remote/internal/inet/SocketConnection.java     | 233 ++++++
 .../remote/internal/inet/SocketInetAddress.java    |   0
 .../remote/internal/inet/TcpIncomingConnector.java | 129 ++++
 .../remote/internal/inet/TcpOutgoingConnector.java |  81 ++
 .../internal/protocol/AbstractPayloadMessage.java  |   0
 .../remote/internal/protocol/ChannelAvailable.java |   0
 .../internal/protocol/ChannelUnavailable.java      |   0
 .../remote/internal/protocol/ConnectRequest.java   |   0
 .../internal/protocol/ConsumerAvailable.java       |  36 +
 .../remote/internal/protocol/ConsumerMessage.java  |  64 ++
 .../remote/internal/protocol/ConsumerReady.java    |  24 +
 .../remote/internal/protocol/ConsumerStopped.java  |  24 +
 .../remote/internal/protocol/ConsumerStopping.java |  24 +
 .../internal/protocol/ConsumerUnavailable.java     |  24 +
 .../remote/internal/protocol/DiscoveryMessage.java |   0
 .../protocol/DiscoveryProtocolSerializer.java      |   0
 .../remote/internal/protocol/EndOfStreamEvent.java |   0
 .../remote/internal/protocol/LookupRequest.java    |   0
 .../remote/internal/protocol/MessageCredits.java   |   0
 .../remote/internal/protocol/MethodMetaInfo.java   |   0
 .../internal/protocol/ParticipantAvailable.java    |  70 ++
 .../internal/protocol/ParticipantUnavailable.java  |  58 ++
 .../remote/internal/protocol/PayloadMessage.java   |   0
 .../internal/protocol/ProducerAvailable.java       |  36 +
 .../remote/internal/protocol/ProducerMessage.java  |  64 ++
 .../remote/internal/protocol/ProducerReady.java    |  24 +
 .../remote/internal/protocol/ProducerStopped.java  |  24 +
 .../internal/protocol/ProducerUnavailable.java     |  24 +
 .../internal/protocol/RemoteMethodInvocation.java  |   0
 .../remote/internal/protocol/Request.java          |   0
 .../remote/internal/protocol/RoutableMessage.java  |   0
 .../internal/protocol/RouteAvailableMessage.java   |   0
 .../internal/protocol/RouteUnavailableMessage.java |   0
 .../remote/internal/protocol/StatelessMessage.java |   0
 .../remote/internal/protocol/UnknownMessage.java   |   0
 .../remote/internal/protocol/WorkerStopped.java    |   0
 .../remote/internal/protocol/WorkerStopping.java   |   0
 .../org/gradle/messaging/serialize/Serializer.java |  25 +
 .../actor/internal/DefaultActorFactoryTest.groovy  |   0
 .../messaging/dispatch/AsyncDispatchTest.groovy    |   0
 .../messaging/dispatch/AsyncReceiveTest.groovy     |   0
 .../dispatch/ContextClassLoaderDispatchTest.groovy |   0
 .../ExceptionTrackingFailureHandlerTest.groovy     |   0
 .../dispatch/FailureHandlingDispatchTest.groovy    |   0
 .../messaging/dispatch/MethodInvocationTest.java   |  35 +
 .../dispatch/ProxyDispatchAdapterTest.groovy       |   0
 .../messaging/dispatch/QueuingDispatchTest.groovy  |   0
 .../internal/AsyncConnectionAdapterTest.groovy     |   0
 .../internal/BroadcastSendProtocolTest.groovy      | 139 ++++
 .../remote/internal/BufferingProtocolTest.groovy   |   0
 .../internal/ChannelLookupProtocolTest.groovy      |   0
 .../ChannelRegistrationProtocolTest.groovy         |   0
 .../remote/internal/CompositeAddressTest.groovy    |   0
 .../internal/DefaultMessagingClientTest.groovy     |   0
 .../internal/DefaultMessagingServerTest.groovy     |   0
 .../internal/DefaultObjectConnectionTest.java      | 237 ++++++
 .../DisconnectAwareConnectionDecoratorTest.groovy  | 168 +++++
 .../remote/internal/EagerReceiveBufferTest.groovy  | 135 ++++
 .../remote/internal/GroupMessageFilterTest.groovy  |   0
 .../internal/HandshakeIncomingConnectorTest.groovy |   0
 .../internal/HandshakeOutgoingConnectorTest.groovy |   0
 .../messaging/remote/internal/MessageTest.groovy   |   0
 .../remote/internal/MessagingServicesTest.groovy   |   0
 .../MethodInvocationMarshallingDispatchTest.groovy |   0
 ...ethodInvocationUnmarshallingDispatchTest.groovy |   0
 .../internal/PlaceholderExceptionTest.groovy       |   0
 .../remote/internal/ProtocolStackTest.groovy       |   0
 .../remote/internal/ReceiveProtocolTest.groovy     | 148 ++++
 .../internal/RemoteDisconnectProtocolTest.groovy   |   0
 .../messaging/remote/internal/RouterTest.groovy    |   0
 .../remote/internal/SendProtocolTest.groovy        | 196 +++++
 .../remote/internal/UnicastSendProtocolTest.groovy | 134 ++++
 .../remote/internal/WorkerProtocolTest.groovy      |   0
 .../internal/inet/MultiChoiceAddressTest.groovy    |   0
 .../internal/inet/SocketInetAddressTest.groovy     |   0
 .../inet/TcpConnectorConcurrencyTest.groovy        |  85 +++
 .../remote/internal/inet/TcpConnectorTest.groovy   |  86 +++
 .../protocol/AbstractPayloadMessageTest.groovy     |   0
 .../DiscoveryProcotolSerializerTest.groovy         |  96 +++
 .../protocol/RemoteMethodInvocationTest.java       |   0
 subprojects/migration/migration.gradle             |  26 +
 subprojects/native/native.gradle                   |   1 +
 .../internal/nativeplatform/filesystem/Chmod.java  |  24 +
 .../ComposableFilePermissionHandler.java           |  45 --
 .../filesystem/DefaultFilePathEncoder.java         |  42 ++
 .../nativeplatform/filesystem/EmptyChmod.java      |  25 +
 .../filesystem/FallbackFileStat.java               | 190 -----
 .../nativeplatform/filesystem/FallbackPOSIX.java   | 226 ------
 .../nativeplatform/filesystem/FallbackStat.java    |  30 +
 .../nativeplatform/filesystem/FallbackSymlink.java |  26 +
 .../nativeplatform/filesystem/FilePathEncoder.java |  23 +
 .../filesystem/FilePermissionHandler.java          |  25 -
 .../filesystem/FilePermissionHandlerFactory.java   |  78 --
 .../filesystem/FileSystemServices.java             | 130 ++++
 .../nativeplatform/filesystem/FileSystems.java     |   9 +-
 .../filesystem/GenericFileSystem.java              |  40 +-
 .../nativeplatform/filesystem/LibCStat.java        |  55 ++
 .../nativeplatform/filesystem/LibcChmod.java       |  42 ++
 .../nativeplatform/filesystem/LibcSymlink.java     |  40 +
 .../filesystem/MacFilePathEncoder.java             |  37 +
 .../nativeplatform/filesystem/PosixStat.java       |  34 +
 .../nativeplatform/filesystem/PosixUtil.java       |  16 +-
 .../internal/nativeplatform/filesystem/Stat.java   |  24 +
 .../nativeplatform/filesystem/Symlink.java         |  24 +
 .../jdk7/PosixJdk7FilePermissionHandler.java       |   6 +-
 .../gradle/internal/nativeplatform/jna/LibC.java   |   8 +-
 .../filesystem/CommonFileSystemTest.groovy         |   6 +-
 .../ComposableFilePermissionHandlerTest.groovy     |  51 --
 .../filesystem/FallbackFileStatTest.groovy         |  42 --
 .../filesystem/FallbackPOSIXTest.groovy            |  52 --
 .../FilePermissionHandlerFactoryOnJdk7Test.groovy  |  72 --
 ...ilePermissionHandlerFactoryOnNonJdk7Test.groovy |  94 ---
 .../FileSystemServicesOnLinuxTest.groovy           |  45 ++
 .../filesystem/FileSystemServicesOnMacTest.groovy  |  45 ++
 .../FileSystemServicesOnUnknownOsTest.groovy       |  55 ++
 .../FileSystemServicesOnWindowsTest.groovy         |  45 ++
 .../nativeplatform/filesystem/LibcStatTest.groovy  |  51 ++
 .../nativeplatform/filesystem/PosixUtilTest.groovy |  42 --
 .../jdk7/PosixJdk7FilePermissionHandlerTest.groovy |   7 +-
 .../integtests/openapi/GradleRunnerTest.groovy     |   5 +-
 .../gradle/openapi/external/ExternalUtility.java   |   2 +-
 .../plugins/osgi/OsgiPluginIntegrationSpec.groovy  | 101 +++
 .../internal/plugins/osgi/DefaultOsgiManifest.java | 101 +--
 .../api/internal/plugins/osgi/OsgiHelper.java      |   5 +-
 .../org/gradle/api/plugins/osgi/OsgiManifest.java  |  16 +-
 .../api/plugins/osgi/OsgiPluginConvention.java     |  32 +-
 .../plugins/osgi/DefaultOsgiManifestTest.groovy    | 322 ++++++++
 .../plugins/osgi/DefaultOsgiManifestTest.java      | 250 -------
 .../plugins/osgi/OsgiPluginConventionTest.groovy   |  30 +-
 subprojects/performance/performance.gradle         |  16 +
 subprojects/performance/src/generator.groovy       | 321 +++++++-
 subprojects/performance/src/templates/build.gradle |  25 +-
 subprojects/performance/src/templates/pom.xml      |  48 +-
 .../api/plugins/BasePluginIntegrationTest.groovy   |  45 ++
 .../internal/TaskReportContainerIntegTest.groovy   |   2 +-
 .../api/tasks/bundling/JarIntegrationTest.groovy   | 163 ++++
 .../tasks/bundling/WarTaskIntegrationTest.groovy   | 152 ++++
 .../AntForkingGroovyCompilerIntegrationTest.groovy |   2 +-
 .../ApiGroovyCompilerIntegrationSpec.groovy        |  60 ++
 .../BasicGroovyCompilerIntegrationSpec.groovy      |  53 +-
 .../DaemonGroovyCompilerIntegrationTest.groovy     |   4 +-
 .../compile/GroovyCompilerIntegrationSpec.groovy   |  22 +-
 .../InProcessGroovyCompilerIntegrationTest.groovy  |   4 +-
 .../compile/InvokeDynamicGroovyCompilerSpec.groovy |  42 ++
 .../JreJavaHomeGroovyIntegrationTest.groovy        | 107 +++
 .../gradle/java/JavaPluginGoodBehaviourTest.groovy |   2 +-
 .../JreJavaHomeJavaIntegrationTest.groovy          |  84 +++
 .../InterruptedTestThreadIntegrationTest.groovy    |  52 --
 .../TestOutputListenerIntegrationTest.groovy       |  16 +-
 .../gradle/testing/TestingIntegrationTest.groovy   |  82 ++
 .../testing/junit/JUnitIntegrationTest.groovy      |  20 +-
 .../junit/JUnitLoggingIntegrationTest.groovy       |  78 ++
 .../testing/testng/TestNGIntegrationTest.groovy    |  14 +-
 .../testng/TestNGLoggingIntegrationTest.groovy     |  79 ++
 .../build.gradle                                   |  23 +
 .../src/main/groovy/IntegerCalculations.groovy     |  26 +
 .../build.gradle                                   |  23 +
 .../src/main/groovy/IntegerCalculations.groovy     |  26 +
 .../canUseCustomFileExtensions/build.gradle        |  15 +
 .../src/test/groovy/Person.spec                    |   4 +
 .../src/test/groovy/Person2.groovy                 |   5 +
 .../canListSourceFiles/build.gradle                |   7 +
 .../src/main/groovy/compile/test/Person.groovy     |   3 +
 .../src/main/groovy/compile/test/Person2.groovy    |   3 +
 .../build.gradle                                   |  10 +
 .../src/main/groovy/TestCase.java                  |  20 +
 .../src/main/groovy/TestCaseTransform.java         |  31 +
 .../src/test/groovy/TestCaseTransformTest.groovy   |  28 +
 .../src/test/groovy/TestDelegate.groovy            |   1 +
 .../src/test/groovy/UseBuiltInTransformTest.groovy |   3 +-
 .../src/main/java/MagicInterfaceTransform.java     |   4 -
 .../build.gradle                                   |  23 +
 .../src/main/groovy/MethodInvocations.groovy       |  35 +
 .../src/test/java/org/gradle/OkTest.java           |   1 +
 .../shared/build.gradle                            |  23 +
 .../src/test/groovy/org/gradle/JUnit4Test.groovy   |  46 ++
 .../standardOutputLogging/build.gradle             |  34 +
 .../org/gradle/JUnit4StandardOutputTest.groovy     |  46 ++
 .../src/test/java/org/gradle/OkTest.java           |   4 +
 .../shared/build.gradle                            |  24 +
 .../src/test/groovy/org/gradle/TestNGTest.groovy   |  42 ++
 .../standardOutputLogging/build.gradle             |  35 +
 .../org/gradle/TestNGStandardOutputTest.groovy     |  27 +
 .../gradle/api/internal/plugins/AbstractRule.java  |  27 +
 .../internal/plugins/BuildConfigurationRule.java   |  53 ++
 .../org/gradle/api/internal/plugins/CleanRule.java |  56 ++
 .../gradle/api/internal/plugins/UploadRule.java    |  63 ++
 .../internal/tasks/DefaultSourceSetContainer.java  |   2 +-
 .../internal/tasks/compile/ApiGroovyCompiler.java  |  23 +-
 .../api/internal/tasks/compile/ArgCollector.java   |  23 +
 .../api/internal/tasks/compile/ArgWriter.java      |  82 ++
 .../tasks/compile/CommandLineJavaCompiler.java     |   9 +-
 .../CommandLineJavaCompilerArgumentsGenerator.java |  41 +-
 .../tasks/compile/CompileSpecToArguments.java      |  21 +
 .../tasks/compile/ExecSpecBackedArgCollector.java  |  32 +
 .../compile/InProcessJavaCompilerFactory.java      |   4 +-
 .../tasks/compile/NormalizingGroovyCompiler.java   |  15 +-
 .../tasks/compile/TransformingClassLoader.java     |   6 +-
 .../daemon/InProcessCompilerDaemonFactory.java     |   2 +
 .../tasks/testing/SuiteTestClassProcessor.java     |   2 +-
 .../detection/AbstractTestFrameworkDetector.java   |   2 +-
 .../detection/ClassFileExtractionManager.java      |  23 +-
 .../testing/detection/DefaultTestClassScanner.java |   4 +-
 .../testing/detection/DefaultTestExecuter.java     |   2 +-
 .../testing/junit/JUnitTestClassProcessor.java     |   6 +-
 .../tasks/testing/junit/JUnitTestEventAdapter.java |   4 +-
 .../tasks/testing/junit/JUnitTestFramework.java    |   4 +-
 .../junit/TestClassExecutionEventGenerator.java    |   4 +-
 .../tasks/testing/logging/AbstractTestLogger.java  | 101 +++
 .../logging/ClassMethodNameStackTraceSpec.java     |  35 +
 .../tasks/testing/logging/DefaultTestLogging.java  | 132 +++-
 .../logging/DefaultTestLoggingContainer.java       | 213 ++++++
 .../testing/logging/FullExceptionFormatter.java    | 125 ++++
 .../testing/logging/GroovyStackTraceSpec.java      |  46 ++
 .../testing/logging/ShortExceptionFormatter.java   |  68 ++
 .../tasks/testing/logging/StackTraceFilter.java    |  46 ++
 .../testing/logging/StandardStreamsLogger.java     |  53 --
 .../tasks/testing/logging/TestCountLogger.java     | 107 +++
 .../tasks/testing/logging/TestEventLogger.java     | 119 +++
 .../testing/logging/TestExceptionFormatter.java    |  25 +
 .../testing/logging/TruncatedStackTraceSpec.java   |  32 +
 .../tasks/testing/processors/TestMainAction.java   |   4 +-
 .../testing/results/LoggingResultProcessor.java    |  53 --
 .../internal/tasks/testing/results/TestLogger.java |  88 ---
 .../tasks/testing/results/TestSummaryListener.java |  97 ---
 .../testing/testng/TestNGTestClassProcessor.java   |   4 +-
 .../tasks/testing/testng/TestNGTestFramework.java  |   4 +-
 .../testng/TestNGTestResultProcessorAdapter.java   |   4 +-
 .../internal/tasks/testing/worker/TestWorker.java  |  15 +-
 .../testing/worker/WorkerTestClassProcessor.java   |   2 +-
 .../org/gradle/api/plugins/BasePlugin.groovy       | 185 -----
 .../groovy/org/gradle/api/plugins/BasePlugin.java  | 152 ++++
 .../gradle/api/plugins/JavaPluginConvention.groovy |   2 +-
 .../gradle/api/plugins/ReportingBasePlugin.java    |   2 +-
 .../reporting/internal/DefaultReportContainer.java |   2 +-
 .../reporting/internal/TaskReportContainer.java    |   2 +-
 .../gradle/api/tasks/compile/AbstractOptions.java  |   6 +-
 .../gradle/api/tasks/compile/CompileOptions.java   | 267 +++++--
 .../org/gradle/api/tasks/compile/DebugOptions.java |  20 +-
 .../gradle/api/tasks/compile/DependOptions.java    |  82 +-
 .../org/gradle/api/tasks/compile/ForkOptions.java  |  80 +-
 .../api/tasks/compile/GroovyCompileOptions.java    | 199 ++++-
 .../api/tasks/compile/GroovyForkOptions.java       |  39 +-
 .../groovy/org/gradle/api/tasks/testing/Test.java  | 107 ++-
 .../org/gradle/api/tasks/testing/TestLogging.java  |   9 +-
 .../tasks/testing/logging/TestExceptionFormat.java |  34 +
 .../api/tasks/testing/logging/TestLogEvent.java    |  55 ++
 .../api/tasks/testing/logging/TestLogging.java     | 226 ++++++
 .../testing/logging/TestLoggingContainer.java      | 184 +++++
 .../testing/logging/TestStackTraceFilter.java      |  28 +
 .../api/tasks/testing/logging/package-info.java    |  23 +
 .../api/tasks/testing/testng/TestNGOptions.groovy  |   2 +
 .../api/internal/plugins/unixStartScript.txt       |   2 +-
 .../tasks/DefaultSourceSetContainerTest.java       |   2 +-
 .../internal/tasks/compile/ArgWriterTest.groovy    |  77 ++
 ...ndLineJavaCompilerArgumentsGeneratorTest.groovy |   4 +-
 .../compile/NormalizingGroovyCompilerTest.groovy   |  64 ++
 .../compile/TransformingClassLoaderTest.groovy     |   2 +-
 .../testing/SuiteTestClassProcessorTest.groovy     |   2 +-
 .../junit/JUnitTestClassProcessorTest.groovy       |   2 +-
 .../testing/junit/JUnitTestFrameworkTest.java      |  13 +-
 .../TestClassExecutionEventGeneratorTest.groovy    |   6 +-
 .../testing/logging/AbstractTestLoggerTest.groovy  | 167 +++++
 .../ClassMethodNameStackTraceSpecTest.groovy       |  55 ++
 .../logging/DefaultTestLoggingContainerTest.groovy | 132 ++++
 .../testing/logging/DefaultTestLoggingTest.groovy  |  66 ++
 .../logging/FullExceptionFormatterTest.groovy      | 252 +++++++
 .../logging/GroovyStackTraceSpecTest.groovy        |  77 ++
 .../logging/ShortExceptionFormatterTest.groovy     |  70 ++
 .../testing/logging/SimpleTestDescriptor.groovy    |  32 +
 .../testing/logging/SimpleTestOutputEvent.groovy   |  25 +
 .../tasks/testing/logging/SimpleTestResult.groovy  |  31 +
 .../testing/logging/StackTraceFilterTest.groovy    |  66 ++
 .../logging/StandardStreamsLoggerTest.groovy       | 113 ---
 .../testing/logging/TestCountLoggerTest.groovy     | 153 ++++
 .../testing/logging/TestEventLoggerTest.groovy     | 105 +++
 .../logging/TruncatedStackTraceSpecTest.groovy     |  37 +
 .../testing/processors/TestMainActionTest.groovy   |   2 +-
 .../testing/results/TestListenerAdapterTest.groovy |  24 +-
 .../tasks/testing/results/TestLoggerTest.groovy    | 135 ----
 .../testing/results/TestSummaryListenerTest.groovy | 118 ---
 .../testng/TestNGTestClassProcessorTest.groovy     |   2 +-
 .../testing/testng/TestNGTestFrameworkTest.java    |   9 +-
 .../internal/DefaultReportContainerTest.groovy     |   4 +-
 .../internal/TaskReportContainerTest.groovy        |   2 +-
 .../tasks/compile/GroovyCompileOptionsTest.groovy  |   1 +
 .../org/gradle/api/tasks/testing/TestTest.java     |  55 +-
 .../JreJavaHomeScalaIntegrationTest.groovy         | 107 +++
 .../gradle/plugins/signing/SigningExtension.groovy |   2 +-
 .../gradle/plugins/signing/SigningPlugin.groovy    |   2 +-
 .../gradle/api/plugins/sonar/SonarPlugin.groovy    |   2 +-
 .../tooling/AutoTestedSamplesToolingApiTest.groovy |   8 +-
 .../ConcurrentToolingApiIntegrationSpec.groovy     |   8 +-
 .../SamplesToolingApiIntegrationTest.groovy        |  85 ++-
 .../ToolingApiCompatibilitySuiteRunner.groovy      |   2 +-
 .../fixture/ToolingApiDistributionResolver.groovy  |   4 +-
 ...singCommandLineArgumentsCrossVersionSpec.groovy | 187 +++++
 .../DependencyMetaDataCrossVersionSpec.groovy      |  91 +++
 .../r11rc1/MigrationModelCrossVersionSpec.groovy   |  69 ++
 ...singCommandLineArgumentsCrossVersionSpec.groovy | 187 -----
 .../build.gradle                                   |   9 +
 .../file.txt                                       |   1 +
 .../src/main/java/Person.java                      |   3 +
 .../modelContainsAllProjects/build.gradle          |  18 +
 .../modelContainsAllProjects/settings.gradle       |  17 +
 .../modelContainsAllTestResults/build.gradle       |  21 +
 .../java/org/gradle/tooling/BuildLauncher.java     |   6 +-
 .../org/gradle/tooling/LongRunningOperation.java   |  36 +-
 ...UnsupportedOperationConfigurationException.java |   4 +-
 .../internal/consumer/ConnectionFactory.java       |   2 +-
 .../internal/consumer/ConnectorServices.java       |   2 +-
 .../tooling/internal/consumer/Distribution.java    |   6 +-
 .../internal/consumer/DistributionFactory.java     |  16 +-
 .../internal/consumer/SynchronizedLogging.java     |   9 +-
 .../consumer/async/DefaultAsyncConnection.java     |   4 +-
 .../loader/CachingToolingImplementationLoader.java |   8 +-
 .../loader/DefaultToolingImplementationLoader.java |  10 +-
 .../internal/consumer/versioning/ModelMapping.java |   2 +
 .../eclipse/DefaultEclipseExternalDependency.java  |  11 +-
 .../gradle/DefaultGradleModuleVersion.java         |  59 ++
 .../tooling/internal/idea/DefaultIdeaProject.java  |   5 +-
 .../DefaultIdeaSingleEntryLibraryDependency.java   |  12 +
 .../tooling/internal/migration/DefaultArchive.java |  34 +
 .../internal/migration/DefaultProjectOutput.java   |  81 ++
 .../internal/migration/DefaultTestResult.java      |  34 +
 .../internal/protocol/InternalProjectOutput.java   |  20 +
 .../org/gradle/tooling/model/BuildableElement.java |   2 +-
 .../org/gradle/tooling/model/DomainObjectSet.java  |   8 +-
 .../java/org/gradle/tooling/model/Element.java     |  13 +-
 .../gradle/tooling/model/ExternalDependency.java   |  27 +-
 .../gradle/tooling/model/GradleModuleVersion.java  |  44 ++
 .../org/gradle/tooling/model/GradleProject.java    |   9 +-
 .../java/org/gradle/tooling/model/GradleTask.java  |   2 +-
 .../org/gradle/tooling/model/HasGradleProject.java |   8 +-
 .../gradle/tooling/model/HierarchicalElement.java  |  11 +-
 .../main/java/org/gradle/tooling/model/Model.java  |   5 +-
 .../org/gradle/tooling/model/SourceDirectory.java  |   2 +-
 .../main/java/org/gradle/tooling/model/Task.java   |  13 +-
 .../tooling/model/UnsupportedMethodException.java  |   8 +-
 .../tooling/model/build/BuildEnvironment.java      |   8 +-
 .../tooling/model/build/GradleEnvironment.java     |   4 +-
 .../tooling/model/build/JavaEnvironment.java       |  17 +-
 .../gradle/tooling/model/build/package-info.java   |   2 +-
 .../tooling/model/idea/BasicIdeaProject.java       |   2 +-
 .../tooling/model/idea/IdeaCompilerOutput.java     |   6 +-
 .../gradle/tooling/model/idea/IdeaContentRoot.java |   2 +-
 .../gradle/tooling/model/idea/IdeaDependency.java  |   2 +-
 .../tooling/model/idea/IdeaDependencyScope.java    |   2 +-
 .../tooling/model/idea/IdeaLanguageLevel.java      |   2 +-
 .../org/gradle/tooling/model/idea/IdeaModule.java  |   2 +-
 .../tooling/model/idea/IdeaModuleDependency.java   |   2 +-
 .../org/gradle/tooling/model/idea/IdeaProject.java |  22 +-
 .../idea/IdeaSingleEntryLibraryDependency.java     |   5 +-
 .../tooling/model/idea/IdeaSourceDirectory.java    |   2 +-
 .../gradle/tooling/model/idea/package-info.java    |   2 +-
 .../model/internal/ImmutableDomainObjectSet.java   |  12 +-
 .../tooling/model/internal/migration/Archive.java  |  26 +
 .../model/internal/migration/ProjectOutput.java    |  34 +
 .../model/internal/migration/TaskOutput.java       |  24 +
 .../model/internal/migration/TestResult.java       |  26 +
 .../consumer/DistributionFactoryTest.groovy        |   4 +-
 .../consumer/SynchronizedLoggingTest.groovy        |   4 +-
 .../CachingToolingImplementationLoaderTest.groovy  |   9 +-
 .../DefaultToolingImplementationLoaderTest.groovy  |  10 +-
 subprojects/tooling-api/tooling-api.gradle         |  10 +-
 .../integtests/FavoritesIntegrationTest.java       |  11 +-
 .../org/gradle/foundation/FavoritesTest.java       |  11 +-
 .../gradle/foundation/LiveOutputParserTests.java   |   3 +-
 .../groovy/org/gradle/foundation/TestUtility.java  |  19 +-
 subprojects/website/website.gradle                 | 335 ---------
 1442 files changed, 42546 insertions(+), 26353 deletions(-)

diff --git a/build.gradle b/build.gradle
index f63f4fa..a157357 100644
--- a/build.gradle
+++ b/build.gradle
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-import org.gradle.build.Version
 import org.gradle.build.Install
-import org.gradle.build.Git
-import org.gradle.build.Releases
+import org.gradle.build.TestReportAggregator
 
 /**
  * For building Gradle you usually don't need to specify any properties. Only certain functionality of the Gradle requires
@@ -25,26 +23,20 @@ import org.gradle.build.Releases
  * following properties can be set:
  *
  * Uploading distributions to Gradle's release and snapshot repository at codehaus: artifactoryUserName, artifactoryUserPassword
- * Uploading the userguide and the javadoc to Gradle's website: websiteScpUserName, websiteScpUserPassword
- * Using the build to create a new distribution and install it on the local machine: gradle_installPath
  */
 
-ext.releases = new Releases(project(':core').file('src/releases.xml'), project)
-version = Version.forProject(project)
-
 defaultTasks 'assemble'
-
 apply plugin: 'java-base'
-
 archivesBaseName = 'gradle'
+apply from: "gradle/versioning.gradle"
 
 ext {
     versions = [
         commons_io: 'commons-io:commons-io:1.4'
     ]
     libraries = [
-        ant: dependencies.module('org.apache.ant:ant:1.8.2') {
-            dependency 'org.apache.ant:ant-launcher:1.8.2 at jar'
+        ant: dependencies.module('org.apache.ant:ant:1.8.4') {
+            dependency 'org.apache.ant:ant-launcher:1.8.4 at jar'
         },
         asm: 'asm:asm-all:3.3.1 at jar',
         commons_cli: 'commons-cli:commons-cli:1.2 at jar',
@@ -56,22 +48,22 @@ ext {
         },
         jcip: "net.jcip:jcip-annotations:1.0 at jar",
     ]
-    
+
 }
 
 // Logging
-libraries.slf4j_api = 'org.slf4j:slf4j-api:1.6.4 at jar'
-libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.6.4') {
+libraries.slf4j_api = 'org.slf4j:slf4j-api:1.6.6 at jar'
+libraries.jcl_to_slf4j = dependencies.module('org.slf4j:jcl-over-slf4j:1.6.6') {
     dependency libraries.slf4j_api
 }
-libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.6.4') {
+libraries.jul_to_slf4j = dependencies.module('org.slf4j:jul-to-slf4j:1.6.6') {
     dependency libraries.slf4j_api
 }
-libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.6.4') {
+libraries.log4j_to_slf4j = dependencies.module('org.slf4j:log4j-over-slf4j:1.6.6') {
     dependency libraries.slf4j_api
 }
-libraries.logback_core = 'ch.qos.logback:logback-core:1.0.0 at jar'
-libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.0') {
+libraries.logback_core = 'ch.qos.logback:logback-core:1.0.6 at jar'
+libraries.logback_classic = dependencies.module('ch.qos.logback:logback-classic:1.0.6') {
     dependency libraries.logback_core
     dependency libraries.slf4j_api
 }
@@ -87,10 +79,10 @@ libraries.jetty = dependencies.module("org.mortbay.jetty:jetty:6.1.25") {
     dependency libraries.servlet_api
 }
 
-libraries.commons_httpclient = dependencies.module('org.apache.httpcomponents:httpclient:4.1.2') {
-    dependency "org.apache.httpcomponents:httpcore:4.1.2 at jar"
+libraries.commons_httpclient = dependencies.module('org.apache.httpcomponents:httpclient:4.2.1') {
+    dependency "org.apache.httpcomponents:httpcore:4.2.1 at jar"
     dependency libraries.jcl_to_slf4j
-    dependency "commons-codec:commons-codec:1.4 at jar"
+    dependency "commons-codec:commons-codec:1.6 at jar"
     dependency "org.samba.jcifs:jcifs:1.3.17"
 }
 
@@ -99,11 +91,12 @@ libraries.maven_ant_tasks = dependencies.module("org.apache.maven:maven-ant-task
 }
 
 libraries += [
-        ant_junit: 'org.apache.ant:ant-junit:1.8.2 at jar',
-        ant_antlr: 'org.apache.ant:ant-antlr:1.8.2 at jar',
+        ant_junit: 'org.apache.ant:ant-junit:1.8.4 at jar',
+        ant_antlr: 'org.apache.ant:ant-antlr:1.8.4 at jar',
         antlr: 'antlr:antlr:2.7.7 at jar',
         dom4j: 'dom4j:dom4j:1.6.1 at jar',
-        guava: 'com.google.guava:guava:11.0.1 at jar',
+        guava: 'com.google.guava:guava:11.0.2 at jar',
+        jsr305: 'com.google.code.findbugs:jsr305:1.3.9',
         groovy: 'org.codehaus.groovy:groovy-all:1.8.6 at jar',
         jaxen: 'jaxen:jaxen:1.1 at jar',
         jcip: "net.jcip:jcip-annotations:1.0",
@@ -113,6 +106,15 @@ libraries += [
         nekohtml: 'net.sourceforge.nekohtml:nekohtml:1.9.14'
 ]
 
+libraries.maven3_settings_builder = dependencies.module("org.apache.maven:maven-settings-builder:3.0.4") {
+    dependency "org.apache.maven:maven-settings:3.0.4 at jar"
+    dependency "org.codehaus.plexus:plexus-utils:2.0.6 at jar"
+    dependency "org.codehaus.plexus:plexus-interpolation:1.14 at jar"
+    dependency "org.codehaus.plexus:plexus-component-annotations:1.5.5 at jar"
+    dependency "org.sonatype.plexus:plexus-cipher:1.4 at jar"
+    dependency "org.sonatype.plexus:plexus-sec-dispatcher:1.3 at jar"
+}
+
 libraries.spock = ['org.spockframework:spock-core:0.6-groovy-1.8 at jar',
         libraries.groovy,
         'org.objenesis:objenesis:1.2',
@@ -120,7 +122,7 @@ libraries.spock = ['org.spockframework:spock-core:0.6-groovy-1.8 at jar',
 libraries.jmock = ['org.jmock:jmock:2.5.1',
         'org.hamcrest:hamcrest-core:1.1',
         'org.hamcrest:hamcrest-library:1.1',
-        'org.jmock:jmock-junit4:2.5.1',
+        dependencies.create('org.jmock:jmock-junit4:2.5.1') { exclude group: 'junit', module: 'junit-dep' }, //junit-dep pulls old definitions of core junit types.
         'org.jmock:jmock-legacy:2.5.1',
         'org.objenesis:objenesis:1.2',
         'cglib:cglib-nodep:2.2']
@@ -138,20 +140,10 @@ allprojects {
         maven { url 'http://repository.codehaus.org/' }
     }
 
-    configurations {
-        all {
-            resolutionStrategy {
-                //we cannot use 'hours' for now due to java 1.5 problem
-                cacheDynamicVersionsFor 24*60*60, 'seconds'
-                cacheChangingModulesFor 24*60*60, 'seconds'
-            }
-        }
-    }
-
     version = this.version
 
     apply from: "$rootDir/gradle/conventions-dsl.gradle"
-    
+
     ext {
         isDevBuild = {
             gradle.taskGraph.hasTask(developerBuild)
@@ -164,22 +156,6 @@ allprojects {
         isCommitBuild = {
             gradle.taskGraph.hasTask(commitBuild)
         }
-
-        isFinalReleaseBuild = {
-            gradle.taskGraph.hasTask(releaseVersion)
-        }
-
-        isRcBuild = {
-            gradle.taskGraph.hasTask(rcVersion)
-        }
-
-        isNightlyBuild = {
-            gradle.taskGraph.hasTask(nightlyVersion)
-        }
-
-        isReleaseBuild = { // Are we doing any kind of “release”? i.e. final, nightly or rc
-            isFinalReleaseBuild() || isNightlyBuild() || isRcBuild()
-        }
     }
 }
 
@@ -240,6 +216,76 @@ task aggregateTestReports(type: TestReportAggregator) {
     projects = subprojects
 }
 
+task determineCommitId {
+    ext.commitId = null
+
+    doLast {
+        def strategies = []
+
+        def env = System.getenv()
+
+        // Builds of Gradle happening on the CI server
+        strategies << {
+            env["BUILD_VCS_NUMBER"]
+        }
+
+        // For the discovery builds, this points to the Gradle revision
+        strategies << {
+            env.find { it.key.startsWith("BUILD_VCS_NUMBER_Gradle_Master") }?.value
+        }
+
+        // If it's a checkout, ask Git for it
+        strategies << {
+            if (file(".git").exists()) {
+                def baos = new ByteArrayOutputStream()
+                exec {
+                    executable "git"
+                    args "log", "-1", "--format=%H"
+                    standardOutput = baos
+                }
+                new String(baos.toByteArray(), "utf8").trim()
+            } else {
+                null
+            }
+        }
+
+        for (strategy in strategies) {
+            commitId = strategy()
+            if (commitId) {
+                break
+            }
+        }
+        if (!commitId) {
+            throw new InvalidUserDataException("Could not determine commit id")
+        }
+    }
+}
+
+task createBuildReceipt(dependsOn: determineCommitId) {
+    ext.receiptFile = file("$buildDir/build-receipt.properties")
+    outputs.file receiptFile
+    outputs.upToDateWhen { false }
+    doLast {
+        def data = [
+            commitId:  determineCommitId.commitId,
+            versionNumber: version,
+            buildTimestamp: buildTimestamp,
+            username: System.properties["user.name"],
+            hostname: InetAddress.localHost.hostName,
+            javaVersion: System.properties["java.version"],
+            osName: System.properties["os.name"],
+            osVersion: System.properties["os.version"]
+        ]
+
+        receiptFile.parentFile.mkdirs()
+
+        // We write this out ourself instead of using the properties class to avoid the
+        // annoying timestamp that insists on placing in there, that throws out incremental.
+        def content = data.entrySet().collect { "$it.key=$it.value" }.sort().join("\n")
+        receiptFile.setText(content, "ISO-8859-1")
+    }
+}
+
 ext.zipRootFolder = "$archivesBaseName-${-> version}"
 
 ext.binDistImage = copySpec {
@@ -312,10 +358,14 @@ task srcZip(type: Zip) {
     }
 }
 
+task outputsZip(type: Zip) {
+    archiveName "outputs.zip"
+    from(createBuildReceipt)
+    ["all", "bin", "src"].each { from(tasks["${it}Zip"]) }
+}
+
 artifacts {
-    tasks.withType(Zip).each {
-        dists it
-    }
+    dists allZip, binZip, srcZip
 }
 
 task intTestImage(type: Sync) {
@@ -328,12 +378,6 @@ task intTestImage(type: Sync) {
     }
 }
 
-gradle.taskGraph.whenReady {
-    if (([isFinalReleaseBuild(), isNightlyBuild(), isRcBuild()].findAll { it }).size() > 1) {
-        throw new GradleException("This appears to be more than one type of release: final - ${isFinalReleaseBuild()}, rc - ${isRcBuild()}, nightly - ${isNightlyBuild() }")
-    }
-}
-
 def guessMaxForks(project) {
     if (project.hasProperty("maxParallelForks")) {
         return Integer.valueOf(project.getProperty("maxParallelForks"))
@@ -347,7 +391,7 @@ task install(type: Install) {
     group = 'build'
     dependsOn binZip.taskDependencies
     with binDistImage
-    installDirProperyName = 'gradle_installPath'
+    installDirPropertyName = 'gradle_installPath'
 }
 
 task installAll(type: Install) {
@@ -355,7 +399,7 @@ task installAll(type: Install) {
     group = 'build'
     dependsOn allZip.taskDependencies
     with allDistImage
-    installDirProperyName = 'gradle_installPath'
+    installDirPropertyName = 'gradle_installPath'
 }
 
 task testedDists(dependsOn: [check]) {
@@ -398,15 +442,10 @@ task commitBuild {
     dependsOn testedDists
 }
 
-task nightlyVersion
-
-task checkJavaVersion << {
+task verifyIsProductionBuildEnvironment << {
     assert Jvm.current().isJava7() : "Must use a Java 7 compatible JVM to perform this build. Current JVM is ${Jvm.current()}"
-}
-
-task nightlyBuild {
-    description = 'Nightly build performed by the CI server'
-    dependsOn checkJavaVersion, nightlyVersion, testedDists, "uploadAll"
+    def systemCharset = java.nio.charset.Charset.defaultCharset().name()
+    assert systemCharset == "UTF-8" : "Platform encoding must be UTF-8. Is currently $systemCharset. Set -Dfile.encoding=UTF-8."
 }
 
 gradle.taskGraph.whenReady {graph ->
@@ -415,49 +454,6 @@ gradle.taskGraph.whenReady {graph ->
     }
 }
 
-// A marker task which causes the release version to be used when it is present in the task graph
-task releaseVersion
-//TODO SF - this task name is inconsistent because other releaseXxx tasks actually upload some content somewhere. Should be called something like 'markReleaseVersion'
-
-// A marker task which indicates that we are building an rc of some sort
-task rcVersion
-
-//TODO RG - we depend here on the alphabetical order of dependent task execution. We Should change that to fail early if javaversion is inappropriate
-task rc {
-    description "Builds a release candidate for the next release"
-    dependsOn checkJavaVersion, rcVersion, testedDists, "uploadAll"
-}
-
-task tag(type: Tag)
-
-task testedTag(type: Tag, dependsOn: testedDists)
-
-task releaseArtifacts {
-    description = 'Builds the release artifacts'
-    //TODO SF - this task name is inconsistent because other releaseXxx tasks actually upload some content somewhere. Should be called something like 'buildReleaseArtifacts'
-    group = 'release'
-    dependsOn releaseVersion, assemble
-}
-
-//TODO RG - we depend here on the alphabetical order of dependent task execution. We Should change that to fail early if javaversion is inappropriate
-task release {
-    description = 'Builds, tests and uploads the release artifacts'
-    group = 'release'
-    dependsOn releaseVersion, checkJavaVersion, testedTag, releaseArtifacts, testedDists, 'uploadAll'
-    doLast {
-        releases.incrementNextVersion()
-    }
-}
-
-task incrementNextVersion << {
-    releases.incrementNextVersion()
-}
-
-task uploadAll {
-    description = 'Uploads binaries, sources and documentation. Does not upload the website! Useful when release procedure breaks at upload and only upload tasks should executed again'
-    dependsOn uploadArchives, "website:uploadDistributions", "website:uploadDocs", "website:pushReleasesXml"
-}
-
 def wrapperUpdateTask = { name, label ->
     task "${name}Wrapper"(type: Wrapper) {
         doFirst {
@@ -483,58 +479,16 @@ wrapperUpdateTask("rc", "release-candidate")
 wrapperUpdateTask("current", "current")
 
 def groovyProjects() {
-    subprojects.findAll { !(it.name in ["docs", "website"]) }
+    subprojects.findAll { !(it.name in ["docs"]) }
 }
 
 def publishedProjects() {
-    [project(':core'), project(':toolingApi'), project(':wrapper'), project(':baseServices')]
+    [project(':core'), project(':toolingApi'), project(':wrapper'), project(':baseServices'), project(':messaging')]
 }
 
 def pluginProjects() {
-    ['plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven', 'ide', 'announce', 'scala', 'sonar', 'signing', 'cpp', 'ear'].collect {
+    ['plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven', 'ide', 'announce', 'scala', 'sonar', 'signing', 'cpp', 'ear', 'javascript'].collect {
         project(it)
     }
 }
 
-class TestReportAggregator extends Copy {
-    def projects
-
-    File testResultsDir
-
-    @OutputDirectory
-    File testReportDir
-
-    def TestReportAggregator() {
-        dependsOn { testTasks }
-        from { inputTestResultDirs }
-        into { testResultsDir }
-    }
-
-    @TaskAction
-    def aggregate() {
-        def report = new org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport(testReportDir: testReportDir, testResultsDir: testResultsDir)
-        report.generateReport()
-    }
-
-    def getTestTasks() {
-        projects.collect { it.tasks.withType(Test) }.flatten()
-    }
-
-    def getInputTestResultDirs() {
-        testTasks*.testResultsDir
-    }
-
-}
-
-class Tag extends DefaultTask {
-    @TaskAction
-    def tagNow() {
-        def version = project.version
-        def git = new Git(project)
-        git.checkNoModifications()
-        git.tag("REL_$version", "Release $version")
-        git.branch("RB_$version")
-    }
-}
-
-
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index f422e07..d44058d 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -27,7 +27,7 @@ repositories {
 
 dependencies {
     compile gradleApi()
-    compile 'com.google.guava:guava:11.0.1 at jar'
+    compile 'com.google.guava:guava:11.0.2 at jar'
     compile 'commons-lang:commons-lang:2.6 at jar'
     groovy localGroovy()
     testCompile 'junit:junit:4.10 at jar'
@@ -35,16 +35,7 @@ dependencies {
 
     compile "org.pegdown:pegdown:1.1.0"
     compile "org.jsoup:jsoup:1.6.3"
-}
-
-configurations {
-	all {
-		resolutionStrategy {
-            //we cannot use 'hours' for now due to java 1.5 problem
-			cacheDynamicVersionsFor 24*60*60, 'seconds'
-			cacheChangingModulesFor 24*60*60, 'seconds'
-		}
-	}
+    compile "com.googlecode.jarjar:jarjar:1.3"
 }
 
 apply from: '../gradle/compile.gradle'
diff --git a/buildSrc/src/main/groovy/org/gradle/build/GenerateReleasesXml.groovy b/buildSrc/src/main/groovy/org/gradle/build/GenerateReleasesXml.groovy
deleted file mode 100644
index 1aea477..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/GenerateReleasesXml.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.build
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-
-class GenerateReleasesXml extends DefaultTask {
-    @Input
-    String getVersion() { return project.version.versionNumber }
-
-    @Input
-    Date getBuildTime() { return project.version.buildTime }
-
-    @OutputFile
-    File destFile
-
-    @InputFile
-    File getSrcFile() { return project.releases.releasesFile }
-
-    @TaskAction
-    def void generate() {
-        logger.info('Write release xml to: {}', destFile)
-        project.releases.generateTo(destFile)
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/Git.groovy b/buildSrc/src/main/groovy/org/gradle/build/Git.groovy
deleted file mode 100644
index 715a195..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/Git.groovy
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.build
-
-import org.gradle.api.Project
-
-class Git {
-    private final Project project
-
-    def Git(Project project) {
-        this.project = project
-    }
-
-    def checkNoModifications() {
-        println 'checking for modifications'
-        def stdout = new ByteArrayOutputStream()
-        project.exec {
-            executable = 'git'
-            args = ['status', '--porcelain']
-            standardOutput = stdout
-        }
-        if (stdout.toByteArray().length > 0) {
-            throw new RuntimeException('Uncommited changes found in the source tree:\n' + stdout.toString())
-        }
-    }
-
-    def tag(String tag, String message) {
-        println "tagging with $tag"
-        project.exec {
-            executable = 'git'
-            args = ['tag', '-a', tag, '-m', message]
-        }
-    }
-
-    def branch(String branch) {
-        println "creating branch $branch"
-        project.exec {
-            executable = 'git'
-            args = ['branch', branch]
-        }
-    }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/build/Install.groovy b/buildSrc/src/main/groovy/org/gradle/build/Install.groovy
index 8352798..6ede041 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/Install.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/Install.groovy
@@ -19,7 +19,7 @@ import org.gradle.api.tasks.Sync
 
 class Install extends Sync {
 
-    String installDirProperyName
+    String installDirPropertyName
     File installDir
 
     def Install() {
@@ -33,10 +33,10 @@ class Install extends Sync {
         project.gradle.taskGraph.whenReady {graph ->
             if (graph.hasTask(path)) {
                 // Do this early to ensure that the properties we need have been set, and fail early
-                if (!project.hasProperty(installDirProperyName)) {
-                    throw new RuntimeException("You can't install without setting the $installDirProperyName property.")
+                if (!project.hasProperty(installDirPropertyName)) {
+                    throw new RuntimeException("You can't install without setting the $installDirPropertyName property.")
                 }
-                installDir = project.file(this.project."$installDirProperyName")
+                installDir = project.file(this.project."$installDirPropertyName")
                 if (installDir.file) {
                     throw new RuntimeException("Install directory $installDir does not look like a Gradle installation. Cannot delete it to install.")
                 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/JarJar.groovy b/buildSrc/src/main/groovy/org/gradle/build/JarJar.groovy
new file mode 100644
index 0000000..afe9b10
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/JarJar.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 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 org.gradle.build
+
+import com.tonicsystems.jarjar.Main as JarJarMain
+import org.gradle.api.DefaultTask
+import org.gradle.api.GradleException
+import org.gradle.api.internal.file.TemporaryFileProvider
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+
+class JarJar extends DefaultTask {
+    @InputFile File inputFile
+    @OutputFile File outputFile
+
+    @Input def rules = [:]
+    @Input def keeps = []
+
+    @TaskAction
+    void nativeJarJar() {
+        try {
+            TemporaryFileProvider tmpFileProvider = getServices().get(TemporaryFileProvider);
+            File tempRuleFile = tmpFileProvider.createTemporaryFile("jarjar", "rule")
+            writeRuleFile(tempRuleFile)
+
+            JarJarMain.main("process", tempRuleFile.absolutePath, inputFile.absolutePath, outputFile.absolutePath)
+        } catch (IOException e) {
+            throw new GradleException("Unable to execute JarJar task", e);
+        }
+    }
+
+    void rule(String pattern, String result) {
+        rules[pattern] = result
+    }
+
+    void keep(String pattern) {
+        keeps << pattern
+    }
+
+    private void writeRuleFile(File ruleFile) {
+        ruleFile.withPrintWriter { writer ->
+            rules.each {pattern, result ->
+                writer.println("rule ${pattern} ${result}")
+            }
+            keeps.each {pattern ->
+                writer.println("keep ${pattern}")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/build/Releases.groovy b/buildSrc/src/main/groovy/org/gradle/build/Releases.groovy
deleted file mode 100644
index 67f2e85..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/Releases.groovy
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.build
-
-import java.util.regex.Pattern
-import java.text.SimpleDateFormat
-import org.gradle.api.Project
-
-class Releases {
-    final File releasesFile
-    final Project project
-
-    Releases(File releasesFile, Project project) {
-        this.releasesFile = releasesFile
-        this.project = project
-    }
-
-    String getNextVersion() {
-        def releases = load()
-        def next = releases.next
-        assert next && next[0].'@version'
-        return next[0].'@version'
-    }
-
-    void generateTo(File resourceFile) {
-        modifyTo(resourceFile) {
-            next.each { remove(it) }
-            current[0].'@version' = this.project.version.versionNumber
-            current[0].'@build-time' = this.formattedBuildTime
-            current[0].'@type' = this.project.version.release ? 'release' : 'snapshot'
-        }
-    }
-
-    String calculateNextVersion(String version) {
-        def matcher = Pattern.compile("(\\d+(\\.\\d+)*?\\.)(\\d+)((-\\w+-)(\\d)[a-z]?)?").matcher(version)
-        if (!matcher.matches()) {
-            throw new RuntimeException("Cannot determine the next version after '$version'")
-        }
-        if (!matcher.group(4)) {
-            def minor = matcher.group(3) as Integer
-            return "${matcher.group(1)}${minor+1}-milestone-1"
-        }
-        def minor = matcher.group(6) as Integer
-        return "${matcher.group(1)}${matcher.group(3)}${matcher.group(5)}${minor+1}"
-    }
-
-    void incrementNextVersion() {
-        modifyTo(releasesFile) {
-            def nextRelease = next[0]
-            assert nextRelease && nextRelease.'@version'
-            def thisRelease = nextRelease.'@version'
-            nextRelease. at version = this.calculateNextVersion(thisRelease)
-            def currentRelease = current[0]
-            assert currentRelease
-            currentRelease + {
-                release(version: thisRelease, "build-time": this.formattedBuildTime)
-            }
-        }
-    }
-
-    private String getFormattedBuildTime() {
-        return new SimpleDateFormat("yyyyMMddHHmmssZ").format(project.version.buildTime)
-    }
-
-    private load() {
-        assert releasesFile.exists()
-        new XmlParser().parse(releasesFile)
-    }
-
-    public modifyTo(File destination, Closure modifications) {
-        def releases = load()
-        project.configure(releases, modifications)
-        destination.parentFile.mkdirs()
-        destination.createNewFile()
-        destination.withPrintWriter { writer -> new XmlNodePrinter(writer, "  ").print(releases) }
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/TestReportAggregator.groovy b/buildSrc/src/main/groovy/org/gradle/build/TestReportAggregator.groovy
new file mode 100644
index 0000000..2c5ed5f
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/TestReportAggregator.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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 org.gradle.build
+
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.testing.Test;
+
+class TestReportAggregator extends Copy {
+    def projects
+
+    File testResultsDir
+
+    @OutputDirectory
+    File testReportDir
+
+    def TestReportAggregator() {
+        dependsOn { testTasks }
+        from { inputTestResultDirs }
+        into { testResultsDir }
+    }
+
+    @TaskAction
+    def aggregate() {
+        def report = new org.gradle.api.internal.tasks.testing.junit.report.DefaultTestReport(testReportDir: testReportDir, testResultsDir: testResultsDir)
+        report.generateReport()
+    }
+
+    def getTestTasks() {
+        projects.collect { it.tasks.withType(Test) }.flatten()
+    }
+
+    def getInputTestResultDirs() {
+        testTasks*.testResultsDir
+    }
+
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/Version.groovy b/buildSrc/src/main/groovy/org/gradle/build/Version.groovy
deleted file mode 100644
index 56c533e..0000000
--- a/buildSrc/src/main/groovy/org/gradle/build/Version.groovy
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.build
-
-import java.text.SimpleDateFormat
-import org.gradle.api.GradleException
-import org.gradle.api.Project
-
-class Version {
-
-    enum Type {
-        ADHOC({ !it.isReleaseBuild() }, { it }), 
-        NIGHTLY({ it.isNightlyBuild() }, { "nightly" }), 
-        RC({ it.isRcBuild() }, { "release-candidate" }), 
-        FINAL({ it.isFinalReleaseBuild() }, { it })
-        
-        final Closure detector
-        final Closure labelProvider
-        
-        Type(Closure detector, Closure labelProvider) {
-            this.detector = detector
-            this.labelProvider = labelProvider
-        }
-    }
-
-    private final Closure versionNumberProvider
-    final Date buildTime
-    private final Closure typeProvider
-
-    static forProject(Project project) {
-        def versionNumber = project.releases.nextVersion
-        File timestampFile = new File(project.buildDir, 'timestamp.txt')
-        if (timestampFile.isFile()) {
-            boolean uptodate = true
-            def modified = timestampFile.lastModified()
-            project.project(':core').fileTree('src/main').visit {fte ->
-                if (fte.file.isFile() && fte.lastModified > modified) {
-                    uptodate = false
-                    fte.stopVisiting()
-                }
-            }
-            if (!uptodate) {
-                timestampFile.setLastModified(new Date().time)
-            }
-        } else {
-            timestampFile.parentFile.mkdirs()
-            timestampFile.createNewFile()
-        }
-        def buildTime = createDateFormat().format(new Date(timestampFile.lastModified()))
-
-        def type = null
-        project.gradle.taskGraph.whenReady { graph ->
-            type = Type.values().find { it.detector(project) }
-            if (type != Type.FINAL) {
-                versionNumber += "-" + buildTime
-            }
-        }
-
-        def typeProvider = {
-            if (type == null) {
-                throw new GradleException("Can't determine whether the type of version for this build before the task graph is populated")
-            }
-            type
-        }
-
-        new Version({ versionNumber }, buildTime, typeProvider)
-    }
-
-    Version(Closure versionNumberProvider, String buildTime, Closure typeProvider) {
-        this(versionNumberProvider, createDateFormat().parse(buildTime), typeProvider)
-    }
-
-    Version(Closure versionNumberProvider, Date buildTime, Closure typeProvider) {
-        this.versionNumberProvider = versionNumberProvider
-        this.buildTime = buildTime
-        this.typeProvider = typeProvider
-    }
-
-    static createDateFormat() {
-        new SimpleDateFormat('yyyyMMddHHmmssZ')
-    }
-
-    String toString() {
-        versionNumber
-    }
-
-    String getVersionNumber() {
-        versionNumberProvider()
-    }
-
-    String getTimestamp() {
-        createDateFormat().format(buildTime)
-    }
-
-    boolean isRelease() {
-        type == Type.FINAL
-    }
-
-    Type getType() {
-        typeProvider()
-    }
-
-    String getLabel() {
-        type.labelProvider(versionNumber)
-    }
-    
-    String getDistributionUrl() {
-        if (release) {
-            'https://gradle.artifactoryonline.com/gradle/distributions'
-        } else {
-            'https://gradle.artifactoryonline.com/gradle/distributions-snapshots'
-        }
-    }
-
-    String getLibsUrl() {
-        if (release) {
-            'https://gradle.artifactoryonline.com/gradle/libs-releases-local'
-        } else {
-            'https://gradle.artifactoryonline.com/gradle/libs-snapshots-local'
-        }
-    }
-
-    def docUrl(docLabel) {
-        "http://www.gradle.org/doc/${-> release ? 'current' : label}/$docLabel"
-    }
-
-    def getJavadocUrl() {
-        docUrl("javadoc")
-    }
-
-    def getGroovydocUrl() {
-        docUrl("groovydoc")
-    }
-
-    def getDsldocUrl() {
-        docUrl("dsl")
-    }
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy
index a1589b8..eedb6ce 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTask.groovy
@@ -37,6 +37,7 @@ import org.gradle.build.docs.model.ClassMetaDataRepository
 import org.gradle.build.docs.model.SimpleClassMetaDataRepository
 import org.gradle.util.Clock
 import org.gradle.build.docs.DocGenerationException
+import org.gradle.api.Transformer
 
 /**
  * Extracts meta-data from the Groovy and Java source files which make up the Gradle DSL. Persists the meta-data to a file
@@ -126,12 +127,11 @@ class ExtractDslMetaDataTask extends SourceTask {
 
     def fullyQualifyAllTypeNames(ClassMetaData classMetaData, TypeNameResolver resolver) {
         try {
-            if (classMetaData.superClassName) {
-                classMetaData.superClassName = resolver.resolve(classMetaData.superClassName, classMetaData)
-            }
-            for (int i = 0; i < classMetaData.interfaceNames.size(); i++) {
-                classMetaData.interfaceNames[i] = resolver.resolve(classMetaData.interfaceNames[i], classMetaData)
-            }
+            classMetaData.resolveTypes(new Transformer<String, String>(){
+                String transform(String i) {
+                    return resolver.resolve(i, classMetaData)
+                }
+            })
             classMetaData.visitTypes(new Action<TypeMetaData>() {
                 void execute(TypeMetaData t) {
                     resolver.resolve(t, classMetaData)
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/SourceMetaDataVisitor.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/SourceMetaDataVisitor.java
index 7e4ee6a..d83da52 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/SourceMetaDataVisitor.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/SourceMetaDataVisitor.java
@@ -21,9 +21,7 @@ import org.codehaus.groovy.antlr.GroovySourceAST;
 import org.codehaus.groovy.antlr.LineColumn;
 import org.codehaus.groovy.antlr.SourceBuffer;
 import org.codehaus.groovy.antlr.treewalker.VisitorAdapter;
-import org.gradle.build.docs.dsl.model.ClassMetaData;
-import org.gradle.build.docs.dsl.model.MethodMetaData;
-import org.gradle.build.docs.dsl.model.TypeMetaData;
+import org.gradle.build.docs.dsl.model.*;
 import org.gradle.build.docs.model.ClassMetaDataRepository;
 
 import java.lang.reflect.Modifier;
@@ -110,6 +108,7 @@ public class SourceMetaDataVisitor extends VisitorAdapter {
                 outerClass.addInnerClassName(className);
                 currentClass.setOuterClassName(outerClass.getClassName());
             }
+            findAnnotations(t, currentClass);
             classStack.addFirst(currentClass);
             allClasses.add(currentClass);
             typeTokens.put(t, currentClass);
@@ -177,13 +176,17 @@ public class SourceMetaDataVisitor extends VisitorAdapter {
         TypeMetaData returnType = extractTypeName(children.current);
         MethodMetaData method = getCurrentClass().addMethod(name, returnType, rawCommentText);
 
+        findAnnotations(t, method);
         extractParameters(t, method);
 
         Matcher matcher = GETTER_METHOD_NAME.matcher(name);
         if (matcher.matches()) {
             int startName = matcher.start(2);
             String propName = name.substring(startName, startName + 1).toLowerCase() + name.substring(startName + 1);
-            getCurrentClass().addReadableProperty(propName, returnType, rawCommentText, method);
+            PropertyMetaData property = getCurrentClass().addReadableProperty(propName, returnType, rawCommentText, method);
+            for (String annotation : method.getAnnotationTypeNames()) {
+                property.addAnnotationTypeName(annotation);
+            }
             return;
         }
 
@@ -250,7 +253,8 @@ public class SourceMetaDataVisitor extends VisitorAdapter {
 
         MethodMetaData getterMethod = currentClass.addMethod(String.format("get%s", StringUtils.capitalize(
                 propertyName)), propertyType, "");
-        currentClass.addReadableProperty(propertyName, propertyType, getJavaDocCommentsBeforeNode(t), getterMethod);
+        PropertyMetaData property = currentClass.addReadableProperty(propertyName, propertyType, getJavaDocCommentsBeforeNode(t), getterMethod);
+        findAnnotations(t, property);
         if (!Modifier.isFinal(modifiers)) {
             MethodMetaData setterMethod = currentClass.addMethod(String.format("set%s", StringUtils.capitalize(
                     propertyName)), TypeMetaData.VOID, "");
@@ -436,6 +440,17 @@ public class SourceMetaDataVisitor extends VisitorAdapter {
         return result;
     }
 
+    private void findAnnotations(GroovySourceAST t, AbstractLanguageElement currentElement) {
+        GroovySourceAST modifiers = t.childOfType(MODIFIERS);
+        if (modifiers != null) {
+            List<GroovySourceAST> children = modifiers.childrenOfType(ANNOTATION);
+            for (GroovySourceAST child : children) {
+                String identifier = extractIdent(child);
+                currentElement.addAnnotationTypeName(identifier);
+            }
+        }
+    }
+
     private String extractIdent(GroovySourceAST t) {
         return t.childOfType(IDENT).getText();
     }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDoc.groovy
index 4197b17..267d746 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDoc.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/BlockDoc.groovy
@@ -18,7 +18,7 @@ package org.gradle.build.docs.dsl.docbook
 import org.w3c.dom.Element
 import org.gradle.build.docs.dsl.model.TypeMetaData
 
-class BlockDoc {
+class BlockDoc implements DslElementDoc {
     private final MethodDoc blockMethod
     private final PropertyDoc blockProperty
     private final TypeMetaData type
@@ -54,7 +54,15 @@ class BlockDoc {
     List<Element> getComment() {
         return blockMethod.comment
     }
-    
+
+    boolean isDeprecated() {
+        return blockProperty.deprecated || blockMethod.deprecated
+    }
+
+    boolean isExperimental() {
+        return blockProperty.experimental || blockMethod.experimental
+    }
+
     PropertyDoc getBlockProperty() {
         return blockProperty
     }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy
index fa18579..5dfd3e9 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy
@@ -26,7 +26,7 @@ import org.gradle.build.docs.dsl.model.MixinMetaData
 import org.gradle.build.docs.dsl.model.ClassExtensionMetaData
 import org.gradle.build.docs.dsl.model.ExtensionMetaData
 
-class ClassDoc {
+class ClassDoc implements DslElementDoc {
     private final String className
     private final String id
     private final String simpleName
@@ -67,13 +67,21 @@ class ClassDoc {
         methodsSection = methodsTable.parentNode
     }
 
-    def getId() { return id }
+    String getId() { return id }
 
     def getName() { return className }
 
     def getSimpleName() { return simpleName }
 
-    def getComment() { return comment }
+    List<Element> getComment() { return comment }
+
+    boolean isDeprecated() {
+        return classMetaData.deprecated
+    }
+
+    boolean isExperimental() {
+        return classMetaData.experimental
+    }
 
     def getClassProperties() { return classProperties }
 
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.groovy
index d8ba60a..8797f14 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDocRenderer.groovy
@@ -47,6 +47,7 @@ class ClassDocRenderer {
                     seg { apilink('class': classDoc.name, style: classDoc.style) }
                 }
             }
+            addWarnings(classDoc, 'class', delegate)
             appendChildren classDoc.comment
         }
     }
@@ -64,10 +65,6 @@ class ClassDocRenderer {
             return
         }
 
-        def propertyTableHeader = propertiesTable.thead[0].tr[0]
-        def cells = propertyTableHeader.td.collect { it }
-        cells = cells.subList(1, cells.size())
-
         propertiesTable.children = {
             title("Properties - $classDoc.simpleName")
             thead {
@@ -94,6 +91,7 @@ class ClassDocRenderer {
                                 text(' (read-only)')
                             }
                         }
+                        addWarnings(propDoc, 'property', delegate)
                         appendChildren propDoc.comment
                         if (propDoc.additionalValues) {
                             segmentedlist {
@@ -173,6 +171,7 @@ class ClassDocRenderer {
                             }
                             text(')')
                         }
+                        addWarnings(method, 'method', delegate)
                         appendChildren method.comment
                     }
                 }
@@ -219,6 +218,7 @@ class ClassDocRenderer {
                         title {
                             literal(block.name); text(' { }')
                         }
+                        addWarnings(block, 'script block', delegate)
                         appendChildren block.comment
                         segmentedlist {
                             segtitle('Delegates to')
@@ -243,6 +243,27 @@ class ClassDocRenderer {
         }
     }
 
+    void addWarnings(DslElementDoc elementDoc, String description, Object delegate) {
+        if (elementDoc.deprecated) {
+            delegate.caution {
+                para{
+                    text("Note: This $description is ")
+                    link(linkend: 'dsl-element-types', "deprecated")
+                    text(" and will be removed in the next major version of Gradle.")
+                }
+            }
+        }
+        if (elementDoc.experimental) {
+            delegate.caution {
+                para{
+                    text("Note: This $description is ")
+                    link(linkend: 'dsl-element-types', "experimental")
+                    text(" and may change in a future version of Gradle.")
+                }
+            }
+        }
+    }
+
     void mergeExtensions(ClassDoc classDoc) {
         if (!classDoc.classExtensions) {
             return
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslElementDoc.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslElementDoc.java
new file mode 100644
index 0000000..f40cb55
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/DslElementDoc.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 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 org.gradle.build.docs.dsl.docbook;
+
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public interface DslElementDoc {
+    String getId();
+
+    Element getDescription();
+
+    List<Element> getComment();
+
+    boolean isDeprecated();
+
+    boolean isExperimental();
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDoc.groovy
index 81816db..916f29c 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDoc.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/MethodDoc.groovy
@@ -19,7 +19,7 @@ import org.gradle.build.docs.dsl.model.ClassMetaData
 import org.gradle.build.docs.dsl.model.MethodMetaData
 import org.w3c.dom.Element
 
-class MethodDoc {
+class MethodDoc implements DslElementDoc {
     private final String id
     private final MethodMetaData metaData
     private final List<Element> comment
@@ -50,10 +50,18 @@ class MethodDoc {
         return metaData
     }
 
+    boolean isDeprecated() {
+        return metaData.deprecated
+    }
+
+    boolean isExperimental() {
+        return metaData.experimental
+    }
+
     Element getDescription() {
         return comment.find { it.nodeName == 'para' }
     }
-    
+
     List<Element> getComment() {
         return comment
     }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy
index 8b22a4a..45eaef1 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDoc.groovy
@@ -19,7 +19,7 @@ import org.gradle.build.docs.dsl.model.PropertyMetaData
 import org.w3c.dom.Element
 import org.gradle.build.docs.dsl.model.ClassMetaData
 
-class PropertyDoc {
+class PropertyDoc implements DslElementDoc {
     private final String id
     private final String name
     private final List<Element> comment
@@ -57,6 +57,14 @@ class PropertyDoc {
         return metaData
     }
 
+    boolean isDeprecated() {
+        return metaData.deprecated
+    }
+
+    boolean isExperimental() {
+        return metaData.experimental
+    }
+
     Element getDescription() {
         return comment.find { it.nodeName == 'para' }
     }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/AbstractLanguageElement.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/AbstractLanguageElement.java
new file mode 100644
index 0000000..c23cf52
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/AbstractLanguageElement.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 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 org.gradle.build.docs.dsl.model;
+
+import org.gradle.api.Transformer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class AbstractLanguageElement implements LanguageElement, Serializable {
+    private String rawCommentText;
+    private final List<String> annotationNames = new ArrayList<String>();
+
+    protected AbstractLanguageElement() {
+    }
+
+    protected AbstractLanguageElement(String rawCommentText) {
+        this.rawCommentText = rawCommentText;
+    }
+
+    public String getRawCommentText() {
+        return rawCommentText;
+    }
+
+    public void setRawCommentText(String rawCommentText) {
+        this.rawCommentText = rawCommentText;
+    }
+
+    public List<String> getAnnotationTypeNames() {
+        return annotationNames;
+    }
+
+    public void addAnnotationTypeName(String annotationType) {
+        annotationNames.add(annotationType);
+    }
+
+    public boolean isDeprecated() {
+        return annotationNames.contains(Deprecated.class.getName());
+    }
+
+    public boolean isExperimental() {
+        return annotationNames.contains("org.gradle.api.Experimental");
+    }
+
+    public void resolveTypes(Transformer<String, String> transformer) {
+        for (int i = 0; i < annotationNames.size(); i++) {
+            annotationNames.set(i, transformer.transform(annotationNames.get(i)));
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassMetaData.java
index a6adffe..ba3324d 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassMetaData.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/ClassMetaData.java
@@ -17,6 +17,7 @@ package org.gradle.build.docs.dsl.model;
 
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.Action;
+import org.gradle.api.Transformer;
 import org.gradle.build.docs.model.Attachable;
 import org.gradle.build.docs.model.ClassMetaDataRepository;
 import org.gradle.util.GUtil;
@@ -24,13 +25,12 @@ import org.gradle.util.GUtil;
 import java.io.Serializable;
 import java.util.*;
 
-public class ClassMetaData implements Serializable, Attachable<ClassMetaData>, LanguageElement, TypeContainer {
+public class ClassMetaData extends AbstractLanguageElement implements Serializable, Attachable<ClassMetaData>, TypeContainer {
     private final String className;
     private String superClassName;
     private final String packageName;
     private final boolean isInterface;
     private final boolean isGroovy;
-    private final String rawCommentText;
     private final List<String> imports = new ArrayList<String>();
     private final List<String> interfaceNames = new ArrayList<String>();
     private final Map<String, PropertyMetaData> declaredProperties = new HashMap<String, PropertyMetaData>();
@@ -41,11 +41,11 @@ public class ClassMetaData implements Serializable, Attachable<ClassMetaData>, L
     public final HashMap<String, String> constants = new HashMap<String, String>();
 
     public ClassMetaData(String className, String packageName, boolean isInterface, boolean isGroovy, String rawClassComment) {
+        super(rawClassComment);
         this.className = className;
         this.packageName = packageName;
         this.isInterface = isInterface;
         this.isGroovy = isGroovy;
-        this.rawCommentText = rawClassComment;
     }
 
     public ClassMetaData(String className) {
@@ -124,10 +124,6 @@ public class ClassMetaData implements Serializable, Attachable<ClassMetaData>, L
         this.outerClassName = outerClassName;
     }
 
-    public String getRawCommentText() {
-        return rawCommentText;
-    }
-
     public List<String> getImports() {
         return imports;
     }
@@ -239,6 +235,22 @@ public class ClassMetaData implements Serializable, Attachable<ClassMetaData>, L
         return method;
     }
 
+    public void resolveTypes(Transformer<String, String> transformer) {
+        super.resolveTypes(transformer);
+        if (superClassName != null) {
+            superClassName = transformer.transform(superClassName);
+        }
+        for (int i = 0; i < interfaceNames.size(); i++) {
+            interfaceNames.set(i, transformer.transform(interfaceNames.get(i)));
+        }
+        for (PropertyMetaData propertyMetaData : declaredProperties.values()) {
+            propertyMetaData.resolveTypes(transformer);
+        }
+        for (MethodMetaData methodMetaData : declaredMethods) {
+            methodMetaData.resolveTypes(transformer);
+        }
+    }
+
     public void visitTypes(Action<TypeMetaData> action) {
         for (PropertyMetaData propertyMetaData : declaredProperties.values()) {
             propertyMetaData.visitTypes(action);
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/LanguageElement.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/LanguageElement.java
index 5fc9d64..d931ff3 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/LanguageElement.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/LanguageElement.java
@@ -15,6 +15,14 @@
  */
 package org.gradle.build.docs.dsl.model;
 
+import java.util.List;
+
 public interface LanguageElement {
     String getRawCommentText();
+
+    List<String> getAnnotationTypeNames();
+
+    boolean isDeprecated();
+
+    boolean isExperimental();
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java
index deecfff..4bcbcab 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/MethodMetaData.java
@@ -22,11 +22,10 @@ import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
-public class MethodMetaData implements Serializable, LanguageElement, TypeContainer {
+public class MethodMetaData extends AbstractLanguageElement implements Serializable, TypeContainer {
     private final String name;
     private final ClassMetaData ownerClass;
     private final List<ParameterMetaData> parameters = new ArrayList<ParameterMetaData>();
-    private String rawCommentText;
     private TypeMetaData returnType;
 
     public MethodMetaData(String name, ClassMetaData ownerClass) {
@@ -88,13 +87,15 @@ public class MethodMetaData implements Serializable, LanguageElement, TypeContai
         parameters.add(param);
         return param;
     }
-    
-    public String getRawCommentText() {
-        return rawCommentText;
+
+    @Override
+    public boolean isDeprecated() {
+        return super.isDeprecated() || ownerClass.isDeprecated();
     }
 
-    public void setRawCommentText(String rawCommentText) {
-        this.rawCommentText = rawCommentText;
+    @Override
+    public boolean isExperimental() {
+        return super.isExperimental() || ownerClass.isExperimental();
     }
 
     public String getSignature() {
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/PropertyMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/PropertyMetaData.java
index fb3ae2f..91d2128 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/PropertyMetaData.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/model/PropertyMetaData.java
@@ -19,9 +19,8 @@ import org.gradle.api.Action;
 
 import java.io.Serializable;
 
-public class PropertyMetaData implements Serializable, LanguageElement, TypeContainer {
+public class PropertyMetaData extends AbstractLanguageElement implements Serializable, TypeContainer {
     private TypeMetaData type;
-    private String rawCommentText;
     private final String name;
     private final ClassMetaData ownerClass;
     private MethodMetaData setter;
@@ -57,12 +56,14 @@ public class PropertyMetaData implements Serializable, LanguageElement, TypeCont
         return ownerClass;
     }
 
-    public String getRawCommentText() {
-        return rawCommentText;
+    @Override
+    public boolean isDeprecated() {
+        return super.isDeprecated() || ownerClass.isDeprecated();
     }
 
-    public void setRawCommentText(String rawCommentText) {
-        this.rawCommentText = rawCommentText;
+    @Override
+    public boolean isExperimental() {
+        return super.isExperimental() || ownerClass.isExperimental();
     }
 
     public String getSignature() {
diff --git a/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestConvention.groovy b/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestConvention.groovy
new file mode 100644
index 0000000..1cc1658
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestConvention.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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 org.gradle.build.integtest
+
+import org.gradle.api.Project
+
+class IntegTestConvention {
+    private final Project project
+    final List integTests = []
+
+    IntegTestConvention(Project project) {
+        this.project = project
+    }
+
+    String getIntegTestMode() {
+        if (!project.tasks.findByName('ciBuild') || !project.gradle.taskGraph.populated) {
+            return null
+        }
+        if (project.isCIBuild()) {
+            return 'forking'
+        }
+        return System.getProperty("org.gradle.integtest.executer") ?: 'embedded'
+    }
+
+    File getIntegTestUserDir() {
+        return project.file('intTestHomeDir')
+    }
+
+    File getIntegTestImageDir() {
+        if (!project.tasks.findByName('intTestImage')) {
+            return null
+        }
+        return project.intTestImage.destinationDir
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestPlugin.groovy b/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestPlugin.groovy
new file mode 100644
index 0000000..fd6abe3
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/integtest/IntegTestPlugin.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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 org.gradle.build.integtest
+
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+
+class IntegTestPlugin implements Plugin<Project> {
+    public void apply(Project project) {
+        project.convention.plugins.integTest = new IntegTestConvention(project)
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/test/groovy/org/gradle/build/ReleasesTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/ReleasesTest.groovy
deleted file mode 100644
index 98bb2c4..0000000
--- a/buildSrc/src/test/groovy/org/gradle/build/ReleasesTest.groovy
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.build
-
-import spock.lang.Specification
-import java.text.SimpleDateFormat
-import org.gradle.testfixtures.ProjectBuilder
-import org.gradle.api.Project
-import java.text.DateFormat
-
-class ReleasesTest extends Specification {
-    Releases releases
-    Project project
-    File releasesXml
-    final DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssZ")
-    final Date buildTime = new Date()
-
-    def setup() {
-        project = ProjectBuilder.builder().build()
-        releasesXml = project.file('releases.xml')
-        releases = new Releases(releasesXml, project)
-    }
-
-    def "determines next release version from releases xml"() {
-        releasesXml << '''
-<releases>
-    <next version="1.2-preview-45"/>
-    <current version="ignore-me" build-time="ignore-me" type="release"/>
-    <release version="ignore-me" build-time="ignore-me"/>
-</releases>
-'''
-
-        expect:
-        releases.nextVersion == '1.2-preview-45'
-    }
-
-    def "generates resources xml resource"() {
-        def destFile = project.file('dest.xml')
-        releasesXml << '''
-<releases>
-    <next version="1.2-preview-45"/>
-    <current version="ignore-me" build-time="ignore-me"/>
-    <release version="ignore-me" build-time="ignore-me"/>
-</releases>
-'''
-        project.version = [versionNumber: '1.0-milestone-2', buildTime: buildTime]
-
-        when:
-        releases.generateTo(destFile)
-
-        then:
-        destFile.text == """<releases>
-  <current version="1.0-milestone-2" build-time="${dateFormat.format(buildTime)}" type="snapshot"/>
-  <release version="ignore-me" build-time="ignore-me"/>
-</releases>
-"""
-
-        when:
-        project.version.release = true
-        releases.generateTo(destFile)
-
-        then:
-        destFile.text == """<releases>
-  <current version="1.0-milestone-2" build-time="${dateFormat.format(buildTime)}" type="release"/>
-  <release version="ignore-me" build-time="ignore-me"/>
-</releases>
-"""
-    }
-
-    def "calculates next version"() {
-        expect:
-        releases.calculateNextVersion('1.0') == '1.1-milestone-1'
-        releases.calculateNextVersion('1.1.0') == '1.1.1-milestone-1'
-        releases.calculateNextVersion('1.1.2.45') == '1.1.2.46-milestone-1'
-        releases.calculateNextVersion('1.0-milestone-2') == '1.0-milestone-3'
-        releases.calculateNextVersion('1.0-milestone-2a') == '1.0-milestone-3'
-        releases.calculateNextVersion('1.0-rc-2') == '1.0-rc-3'
-    }
-
-    def "updates releases xml To increment next version"() {
-        releasesXml << '''
-<releases>
-  <next version="1.0-milestone-2"/>
-  <current version="${version}" build-time="${build=time}"/>
-  <release version="previous" build-time="20101220123412-0200"/>
-</releases>
-'''
-        project.version = [buildTime: buildTime]
-
-        when:
-        releases.incrementNextVersion()
-
-        then:
-        releasesXml.text == """<releases>
-  <next version="1.0-milestone-3"/>
-  <current version="\${version}" build-time="\${build=time}"/>
-  <release version="1.0-milestone-2" build-time="${dateFormat.format(buildTime)}"/>
-  <release version="previous" build-time="20101220123412-0200"/>
-</releases>
-"""
-    }
-
-}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy
index 2be625e..e4d5301 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/ExtractDslMetaDataTaskTest.groovy
@@ -49,12 +49,14 @@ class ExtractDslMetaDataTaskTest extends Specification {
         groovyClass.rawCommentText.contains('This is a groovy class.')
         groovyClass.superClassName == 'org.gradle.test.A'
         groovyClass.interfaceNames == ['org.gradle.test.GroovyInterface', 'org.gradle.test.JavaInterface']
+        groovyClass.annotationTypeNames == []
 
         def groovyInterface = repository.get('org.gradle.test.GroovyInterface')
         groovyInterface.groovy
         groovyInterface.isInterface()
         groovyInterface.superClassName == null
         groovyInterface.interfaceNames == ['org.gradle.test.Interface1', 'org.gradle.test.Interface2']
+        groovyInterface.annotationTypeNames == []
     }
 
     def extractsClassMetaDataFromJavaSource() {
@@ -76,13 +78,14 @@ class ExtractDslMetaDataTaskTest extends Specification {
         javaClass.rawCommentText.contains('This is a java class.')
         javaClass.superClassName == 'org.gradle.test.A'
         javaClass.interfaceNames == ['org.gradle.test.GroovyInterface', 'org.gradle.test.JavaInterface']
-
+        javaClass.annotationTypeNames == []
 
         def javaInterface = repository.get('org.gradle.test.JavaInterface')
         !javaInterface.groovy
         javaInterface.isInterface()
         javaInterface.superClassName == null
         javaInterface.interfaceNames == ['org.gradle.test.Interface1', 'org.gradle.test.Interface2']
+        javaInterface.annotationTypeNames == []
     }
 
     def extractsPropertyMetaDataFromGroovySource() {
@@ -619,9 +622,95 @@ class ExtractDslMetaDataTaskTest extends Specification {
         paramMethod.parameters[0].type.signature == 'T'
     }
 
+    def extractsClassAnnotationsFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
+        groovyClass.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsClassAnnotationsFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
+        task.source testFile('org/gradle/test/JavaInterfaceWithAnnotation.java')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
+        javaClass.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+
+        def javaInterface = repository.get('org.gradle.test.JavaInterfaceWithAnnotation')
+        javaInterface.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsMethodAnnotationsFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
+        def method = groovyClass.declaredMethods.find { it.name == 'annotatedMethod' }
+        method.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsMethodAnnotationsFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
+        def method = javaClass.declaredMethods.find { it.name == 'annotatedMethod' }
+        method.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsPropertyAnnotationsFromGroovySource() {
+        task.source testFile('org/gradle/test/GroovyClassWithAnnotation.groovy')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def groovyClass = repository.get('org.gradle.test.GroovyClassWithAnnotation')
+        def property = groovyClass.declaredProperties.find { it.name == 'annotatedProperty' }
+        property.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
+    def extractsPropertyAnnotationsFromJavaSource() {
+        task.source testFile('org/gradle/test/JavaClassWithAnnotation.java')
+        task.source testFile('org/gradle/test/JavaAnnotation.java')
+
+        when:
+        task.extract()
+        repository.load(task.destFile)
+
+        then:
+        def javaClass = repository.get('org.gradle.test.JavaClassWithAnnotation')
+        def property = javaClass.declaredProperties.find { it.name == 'annotatedProperty' }
+        property.annotationTypeNames == ['java.lang.Deprecated', 'org.gradle.test.JavaAnnotation']
+    }
+
     def testFile(String fileName) {
         URL resource = getClass().classLoader.getResource(fileName)
-        assert resource != null
+        assert resource != null: "Could not find resource '$fileName'."
         assert resource.protocol == 'file'
         return new File(resource.toURI())
     }
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ClassMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ClassMetaDataTest.groovy
new file mode 100644
index 0000000..1b354f9
--- /dev/null
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ClassMetaDataTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 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 org.gradle.build.docs.dsl.model
+
+import spock.lang.Specification
+
+class ClassMetaDataTest extends Specification {
+    def "is deprecated when @Deprecated annotation is attached to class"() {
+        def notDeprecated = new ClassMetaData("SomeClass")
+        def deprecated = new ClassMetaData("SomeClass")
+        deprecated.addAnnotationTypeName(Deprecated.class.name)
+
+        expect:
+        !notDeprecated.deprecated
+        deprecated.deprecated
+    }
+
+    def "is experimental when @Experimental annotation is attached to class"() {
+        def notExperimental = new ClassMetaData("SomeClass")
+        def experimental = new ClassMetaData("SomeClass")
+        experimental.addAnnotationTypeName("org.gradle.api.Experimental")
+
+        expect:
+        !notExperimental.experimental
+        experimental.experimental
+    }
+}
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/MethodMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/MethodMetaDataTest.groovy
index 5d31ba7..39e0f87 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/MethodMetaDataTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/MethodMetaDataTest.groovy
@@ -127,4 +127,37 @@ class MethodMetaDataTest extends Specification {
         _ * superClassMetaData.superClass >> null
         _ * superClassMetaData.interfaces >> []
     }
+
+    def "is deprecated when @Deprecated is attached to method or owner is deprecated"() {
+        ClassMetaData deprecatedClass = Mock()
+        def notDeprecated = new MethodMetaData('param', owner)
+        def deprecated = new MethodMetaData('param', owner)
+        def ownerDeprecated = new MethodMetaData('param', deprecatedClass)
+
+        given:
+        deprecated.addAnnotationTypeName(Deprecated.class.name)
+        deprecatedClass.deprecated >> true
+
+        expect:
+        !notDeprecated.deprecated
+        deprecated.deprecated
+        ownerDeprecated.deprecated
+    }
+
+    def "is experimental when @Experimental is attached to method or owner is experimental"() {
+        ClassMetaData experimentalClass = Mock()
+        def notExperimental = new MethodMetaData('param', owner)
+        def experimental = new MethodMetaData('param', owner)
+        def ownerExperimental = new MethodMetaData('param', experimentalClass)
+
+        given:
+        experimental.addAnnotationTypeName("org.gradle.api.Experimental")
+        experimentalClass.experimental >> true
+
+        expect:
+        !notExperimental.experimental
+        experimental.experimental
+        ownerExperimental.experimental
+    }
+
 }
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ParameterMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ParameterMetaDataTest.groovy
index 16ef3a6..dd6e0ac 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ParameterMetaDataTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/ParameterMetaDataTest.groovy
@@ -18,10 +18,9 @@ package org.gradle.build.docs.dsl.model
 import spock.lang.Specification
 
 class ParameterMetaDataTest extends Specification {
-    final MethodMetaData method = Mock()
-    final ParameterMetaData parameter = new ParameterMetaData('param', method)
-
-    def formatsSignature() {
+    def "formats signature"() {
+        MethodMetaData method = Mock()
+        def parameter = new ParameterMetaData('param', method)
         def type = new TypeMetaData('org.gradle.SomeType')
         parameter.type = type
         
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/PropertyMetaDataTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/PropertyMetaDataTest.groovy
index 9d655b1..5f63788 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/PropertyMetaDataTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/model/PropertyMetaDataTest.groovy
@@ -98,5 +98,37 @@ class PropertyMetaDataTest extends Specification {
         then:
         p == null
     }
+
+    def "is deprecated when @Deprecated is attached to property or owner is deprecated"() {
+        ClassMetaData deprecatedClass = Mock()
+        def notDeprecated = new PropertyMetaData('param', classMetaData)
+        def deprecated = new PropertyMetaData('param', classMetaData)
+        def ownerDeprecated = new PropertyMetaData('param', deprecatedClass)
+
+        given:
+        deprecated.addAnnotationTypeName(Deprecated.class.name)
+        deprecatedClass.deprecated >> true
+
+        expect:
+        !notDeprecated.deprecated
+        deprecated.deprecated
+        ownerDeprecated.deprecated
+    }
+
+    def "is experimental when @Experimental is attached to property or owner is experimental"() {
+        ClassMetaData experimentalClass = Mock()
+        def notExperimental = new PropertyMetaData('param', classMetaData)
+        def experimental = new PropertyMetaData('param', classMetaData)
+        def ownerExperimental = new PropertyMetaData('param', experimentalClass)
+
+        given:
+        experimental.addAnnotationTypeName("org.gradle.api.Experimental")
+        experimentalClass.experimental >> true
+
+        expect:
+        !notExperimental.experimental
+        experimental.experimental
+        ownerExperimental.experimental
+    }
 }
 
diff --git a/buildSrc/src/test/resources/org/gradle/test/GroovyClassWithAnnotation.groovy b/buildSrc/src/test/resources/org/gradle/test/GroovyClassWithAnnotation.groovy
new file mode 100644
index 0000000..7d6ff77
--- /dev/null
+++ b/buildSrc/src/test/resources/org/gradle/test/GroovyClassWithAnnotation.groovy
@@ -0,0 +1,10 @@
+package org.gradle.test
+
+ at Deprecated @JavaAnnotation
+class GroovyClassWithAnnotation {
+    @Deprecated @JavaAnnotation
+    String annotatedProperty
+
+    @Deprecated @JavaAnnotation
+    void annotatedMethod() { }
+}
diff --git a/buildSrc/src/test/resources/org/gradle/test/JavaClassWithAnnotation.java b/buildSrc/src/test/resources/org/gradle/test/JavaClassWithAnnotation.java
new file mode 100644
index 0000000..af20292
--- /dev/null
+++ b/buildSrc/src/test/resources/org/gradle/test/JavaClassWithAnnotation.java
@@ -0,0 +1,10 @@
+package org.gradle.test;
+
+ at Deprecated @JavaAnnotation
+public class JavaClassWithAnnotation {
+    @Deprecated @JavaAnnotation
+    String getAnnotatedProperty() { return "hi"; }
+
+    @Deprecated @JavaAnnotation
+    void annotatedMethod() { }
+}
diff --git a/buildSrc/src/test/resources/org/gradle/test/JavaInterfaceWithAnnotation.java b/buildSrc/src/test/resources/org/gradle/test/JavaInterfaceWithAnnotation.java
new file mode 100644
index 0000000..44d5d0b
--- /dev/null
+++ b/buildSrc/src/test/resources/org/gradle/test/JavaInterfaceWithAnnotation.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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 org.gradle.test;
+
+ at Deprecated @JavaAnnotation
+public interface JavaInterfaceWithAnnotation {
+    @Deprecated @JavaAnnotation
+    String getAnnotatedProperty();
+
+    @Deprecated @JavaAnnotation
+    void annotatedMethod();
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index a8b4ae4..6bedd42 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -103,6 +103,9 @@
         <property name="headerFile" value="${checkstyleConfigDir}/required-header.txt"/>
     </module>
     <module name="FileTabCharacter"/>
+    <module name="RegexpSingleline">
+        <property name="format" value="To change body of implemented methods use File \| Settings \| File Templates"/>
+    </module>
 
     <!-- allows suppressing using the //CHECKSTYLE:ON //CHECKSTYLE:OFF -->
     <module name="SuppressionCommentFilter"/>
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
index 8f1a87e..ad7a323 100644
--- a/config/checkstyle/suppressions.xml
+++ b/config/checkstyle/suppressions.xml
@@ -14,9 +14,16 @@
               files=".*[/\\]subprojects[/\\]plugins[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]tasks[/\\][^/\\]+"/>
     <suppress checks="JavadocPackage"
               files=".*[/\\]subprojects[/\\]scala[/\\]src[/\\]main[/\\]groovy[/\\]org[/\\]gradle[/\\]api[/\\]tasks[/\\][^/\\]+"/>
+    <suppress checks="JavadocPackage"
+              files=".*[/\\]subprojects[/\\]base-services[/\\]src[/\\]main[/\\]java[/\\]org[/\\]gradle[/\\]api[/\\][^/\\]+"/>
 
     <!-- Don't require api docs for projects only used internally -->
     <suppress checks="Javadoc.*"
               files=".*[/\\]subprojects[/\\]internal-.+[/\\]src[/\\]main[/\\].+"/>
 
+    <!-- JavaScript plugin is incubating -->
+    <suppress checks="Javadoc.*"
+              files=".*[/\\]subprojects[/\\]javascript[/\\].+"/>
+
+
 </suppressions>
\ No newline at end of file
diff --git a/config/codenarc.xml b/config/codenarc.xml
index 88a18be..1daadd3 100644
--- a/config/codenarc.xml
+++ b/config/codenarc.xml
@@ -32,7 +32,7 @@
         </rule-config>
         <rule-config name='FieldName'>
             <property name='finalRegex' value='^[a-z][a-zA-Z0-9]*$'/>
-            <property name='staticFinalRegex' value='^logger$|^[A-Z][A-Z_0-9]*$'/>
+            <property name='staticFinalRegex' value='^logger$|^[A-Z][A-Z_0-9]*$|^serialVersionUID$'/>
         </rule-config>
         <rule-config name='MethodName'>
             <property name='regex' value='^[a-z][\$_a-zA-Z0-9]*$|^.*\s.*$'/>
diff --git a/gradle/classycle.gradle b/gradle/classycle.gradle
index 1d8b4f8..7264cd1 100644
--- a/gradle/classycle.gradle
+++ b/gradle/classycle.gradle
@@ -1,3 +1,4 @@
+
 configurations {
     classycle
 }
@@ -18,12 +19,16 @@ sourceSets.all { sourceSet ->
             }
             ant.taskdef(name: "classycleDependencyCheck", classname: "classycle.ant.DependencyCheckingTask", classpath: configurations.classycle.asPath)
             reportFile.parentFile.mkdirs()
-            ant.classycleDependencyCheck(reportFile: reportFile, failOnUnwantedDependencies: true, mergeInnerClasses: true,
-                """
-                    check absenceOfPackageCycles > 1 in org.gradle.*
-                """
-            ) {
-                fileset(dir: sourceSet.output.classesDir)
+            try {
+                ant.classycleDependencyCheck(reportFile: reportFile, failOnUnwantedDependencies: true, mergeInnerClasses: true,
+                    """
+                        check absenceOfPackageCycles > 1 in org.gradle.*
+                    """
+                ) {
+                    fileset(dir: sourceSet.output.classesDir)
+                }
+            } catch(Exception e) {
+                throw new RuntimeException("Classycle check failed: $e.message. See report at $reportFile", e)
             }
         }
     }
diff --git a/gradle/groovyProject.gradle b/gradle/groovyProject.gradle
index ffe1e17..2368dcb 100644
--- a/gradle/groovyProject.gradle
+++ b/gradle/groovyProject.gradle
@@ -19,11 +19,6 @@ apply from: "$rootDir/gradle/compile.gradle"
 
 test {
     maxParallelForks = guessMaxForks(project)
-    doFirst {
-        if (isCIBuild()) {
-            maxParallelForks = 2
-        }
-    }
 }
 
 tasks.matching { it instanceof Compile || it instanceof GroovyCompile }.all {
@@ -59,7 +54,9 @@ class ClasspathManifest extends DefaultTask {
     @Input
     Properties getProperties() {
         def properties = new Properties()
-        properties.runtime = project.configurations.runtime.fileCollection { it instanceof ExternalDependency }.collect {it.name}.join(',')
+        properties.runtime = project.configurations.runtime.fileCollection {
+            (it instanceof ExternalDependency) || (it instanceof FileCollectionDependency)
+        }.collect {it.name}.join(',')
         properties.projects = project.configurations.runtime.allDependencies.withType(ProjectDependency).collect {it.dependencyProject.archivesBaseName}.join(',')
         return properties
     }
diff --git a/gradle/idea.gradle b/gradle/idea.gradle
index 312f87b..ebc7e74 100644
--- a/gradle/idea.gradle
+++ b/gradle/idea.gradle
@@ -25,7 +25,6 @@ idea {
     project {
         wildcards += ['?*.gradle']
 
-        jdkName = '1.6'
         languageLevel = '1.5'
 
         ipr {
@@ -46,18 +45,23 @@ idea {
                     }
                 }
 
-                // exclude jdk7/** from compilation if current jvm is not JDK7
-                if (!Jvm.current().isJava7()) {
-                    Collection sourceDirs = groovyProjects().collect { project -> project.sourceSets*.allSource*.srcDirs }.flatten()
-                    def findAndExcludeJdk7PackageFolder = { packageFolder ->
-                        if (packageFolder.name == 'jdk7') {
-                            exclude.appendNode('directory', [url: "file://\$PROJECT_DIR\$/${rootProject.relativePath(packageFolder)}", includeSubdirectories: true])
+                // exclude java version specific classes from compilation when not compilable with current java version
+                def excludeSource = { version, sourceFolder ->
+                    if (sourceFolder.exists()) {
+                        sourceFolder.eachDirRecurse { packageFolder ->
+                            if (packageFolder.name == version) {
+                                exclude.appendNode('directory', [url: "file://\$PROJECT_DIR\$/${rootProject.relativePath(packageFolder)}", includeSubdirectories: true])
+                            }
                         }
                     }
-                    sourceDirs.each { sourceFolder ->
-                        if (sourceFolder.exists()) {
-                            sourceFolder.eachDirRecurse(findAndExcludeJdk7PackageFolder)
-                        }
+                }
+                Collection sourceDirs = groovyProjects().collect { project -> project.sourceSets*.allSource*.srcDirs }.flatten()
+                sourceDirs.each { sourceFolder ->
+                    if (!Jvm.current().isJava7()) {
+                        excludeSource("jdk7", sourceFolder)
+                    }
+                    if (!Jvm.current().isJava6Compatible()) {
+                        excludeSource("jdk6", sourceFolder)
                     }
                 }
 
@@ -69,6 +73,10 @@ idea {
                 def gradleSettings = node.appendNode('component', [name: 'GradleSettings'])
                 gradleSettings.appendNode('option', [name: 'SDK_HOME', value: gradle.gradleHomeDir.absolutePath])
 
+                // set compiler heap space
+                def javacSettings = node.appendNode('component', [name: 'JavacSettings'])
+                javacSettings.appendNode('option', [name: 'MAXIMUM_HEAP_SIZE', value: "256"])
+
                 // license header
                 def copyrightManager = node.component.find { it.'@name' == 'CopyrightManager' }
                 copyrightManager. at default = "ASL2"
@@ -235,29 +243,43 @@ idea {
             </configuration>
         '''))
 
-        runManagerConfig.append(new XmlParser().parseText('''
-            <configuration default="false" name="Gradle Precommit Check" type="Application" factoryName="Application">
-              <extension name="coverage" enabled="false" merge="false" />
-              <option name="MAIN_CLASS_NAME" value="org.gradle.testing.internal.util.IdeQuickCheckRunner" />
-              <option name="VM_PARAMETERS" value="" />
-              <option name="PROGRAM_PARAMETERS" value="" />
-              <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
-              <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
-              <option name="ALTERNATIVE_JRE_PATH" value="" />
-              <option name="ENABLE_SWING_INSPECTOR" value="false" />
-              <option name="ENV_VARIABLES" />
-              <option name="PASS_PARENT_ENVS" value="true" />
-              <module name="internalTesting" />
-              <envs />
-              <method />
-            </configuration>
-        '''))
+        def gradleRunners = [
+                "Quick Check (precommit)": "quickCheck --daemon",
+                "Regenerate IDEA metadata": "idea --daemon",
+                "Regenerate Int Test Image": "intTestImage --daemon"
+        ]
 
-        runManagerConfig.append(new XmlParser().parseText('''
-            <list size="2">
-              <item index="0" class="java.lang.String" itemvalue="Application.Gradle" />
-              <item index="1" class="java.lang.String" itemvalue="Application.Gradle Precommit Check" />
+        def runnerClass = "org.gradle.testing.internal.util.GradlewRunner"
+        def runnerClassModule = "internalTesting"
+
+        def listItems = []
+        gradleRunners.each { runnerName, commandLine ->
+            runManagerConfig.append(new XmlParser().parseText("""
+                <configuration default='false' name='${runnerName}' type='Application' factoryName='Application'>
+                  <extension name='coverage' enabled='false' merge='false' />
+                  <option name='MAIN_CLASS_NAME' value='${runnerClass}' />
+                  <option name='VM_PARAMETERS' value='' />
+                  <option name='PROGRAM_PARAMETERS' value='${commandLine}' />
+                  <option name='WORKING_DIRECTORY' value='file://\$PROJECT_DIR\$' />
+                  <option name='ALTERNATIVE_JRE_PATH_ENABLED' value='false' />
+                  <option name='ALTERNATIVE_JRE_PATH' value='' />
+                  <option name='ENABLE_SWING_INSPECTOR' value='false' />
+                  <option name='ENV_VARIABLES' />
+                  <option name='PASS_PARENT_ENVS' value='true' />
+                  <module name='${runnerClassModule}' />
+                  <envs />
+                  <method />
+                </configuration>
+            """))
+
+            listItems << "<item index='${listItems.size() + 1}' class='java.lang.String' itemvalue='Application.${runnerName}' />"
+        }
+
+        runManagerConfig.append(new XmlParser().parseText("""
+            <list size='${listItems.size() + 1}'>
+              <item index='0' class='java.lang.String' itemvalue='Application.Gradle' />
+              ${listItems}
             </list>
-        '''))
+        """))
     }
 }
diff --git a/gradle/integTest.gradle b/gradle/integTest.gradle
index e6f4537..dfa03dd 100644
--- a/gradle/integTest.gradle
+++ b/gradle/integTest.gradle
@@ -1,27 +1,25 @@
 /*
  * Adds an 'integTest' source set, which contains the integration tests for the project.
  */
+import org.gradle.build.integtest.IntegTestPlugin
+
 apply plugin: 'java'
 rootProject.apply plugin: IntegTestPlugin
 
-configurations {
-    integTestCompile {
-        extendsFrom testCompile
-    }
-    integTestRuntime {
-        extendsFrom integTestCompile, testRuntime
+sourceSets {
+    integTest {
+        compileClasspath += main.output + test.output
+        runtimeClasspath += main.output + test.output
     }
 }
 
-dependencies {
-    integTestCompile project(":internalIntegTesting")
+configurations {
+    integTestCompile.extendsFrom testCompile
+    integTestRuntime.extendsFrom testRuntime
 }
 
-sourceSets {
-    integTest {
-        compileClasspath = sourceSets.main.output + sourceSets.test.output + configurations.integTestCompile
-        runtimeClasspath = output + compileClasspath + configurations.integTestRuntime
-    }
+dependencies {
+    integTestCompile project(":internalIntegTesting")
 }
 
 plugins.withType(org.gradle.plugins.ide.idea.IdeaPlugin) { // lazy as plugin not applied yet
@@ -66,9 +64,8 @@ integTestTasks.all {
         systemProperties['integTest.gradleHomeDir'] = integTestImageDir.absolutePath
         systemProperties['integTest.gradleUserHomeDir'] = integTestUserDir.absolutePath
         systemProperties['integTest.samplesdir'] = project(":docs").samplesDir.absolutePath
-        if (isCIBuild()) {
-            maxParallelForks = 4
-        }
+        systemProperties['integTest.distsDir'] = rootProject.distsDir.absolutePath
+        systemProperties['integTest.libsRepo'] = rootProject.file('build/repo')
     }
 }
 
@@ -101,38 +98,4 @@ task integTest(type: Test) {
 
 tasks.findByName("check")?.dependsOn(integTest)
 
-class IntegTestPlugin implements Plugin<Project> {
-    public void apply(Project project) {
-        project.convention.plugins.integTest = new IntegTestConvention(project)
-    }
-}
-
-class IntegTestConvention {
-    private final Project project
-    final List integTests = []
-
-    IntegTestConvention(Project project) {
-        this.project = project
-    }
-
-    String getIntegTestMode() {
-        if (!project.tasks.findByName('ciBuild') || !project.gradle.taskGraph.populated) {
-            return null
-        }
-        if (project.isCIBuild() || project.isReleaseBuild() ) {
-            return 'forking'
-        }
-        return System.getProperty("org.gradle.integtest.executer") ?: 'embedded'
-    }
-
-    File getIntegTestUserDir() {
-        return project.file('intTestHomeDir')
-    }
 
-    File getIntegTestImageDir() {
-        if (!project.tasks.findByName('intTestImage')) {
-            return null
-        }
-        return project.intTestImage.destinationDir
-    }
-}
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index 0f6e9ae..b906c2b 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -52,7 +52,8 @@ uploadArchives {
     doFirst {
         repositories {
             ivy {
-                artifactPattern "${version.libsUrl}/${project.group.replaceAll('\\.', '/')}/${archivesBaseName}/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+                def repo = "https://gradle.artifactoryonline.com/gradle/libs-${isSnapshot ? 'snapshots' : 'releases'}-local"
+                artifactPattern "${repo}/${project.group.replaceAll('\\.', '/')}/${archivesBaseName}/[revision]/[artifact]-[revision](-[classifier]).[ext]"
                 credentials {
                     username = artifactoryUserName
                     password = artifactoryUserPassword
diff --git a/gradle/testFixtures.gradle b/gradle/testFixtures.gradle
index c97205b..f7937a1 100644
--- a/gradle/testFixtures.gradle
+++ b/gradle/testFixtures.gradle
@@ -29,7 +29,7 @@ sourceSets {
 }
 
 dependencies {
-    testFixturesUsageCompile sourceSets.testFixtures.output
+    testFixturesUsageCompile sourceSets.testFixtures.output, sourceSets.main.output
     testFixturesCompile libraries.junit, libraries.jmock, libraries.spock
 }
 
diff --git a/gradle/versioning.gradle b/gradle/versioning.gradle
new file mode 100644
index 0000000..571c1fe
--- /dev/null
+++ b/gradle/versioning.gradle
@@ -0,0 +1,54 @@
+def timestampFormat = new java.text.SimpleDateFormat('yyyyMMddHHmmssZ')
+timestampFormat.timeZone = TimeZone.getTimeZone("UTC")
+
+if (project.hasProperty("buildTimestamp")) {
+    ext.buildTime = timestampFormat.parse(buildTimestamp)
+} else {
+    File timestampFile = file("$buildDir/timestamp.txt")
+    if (timestampFile.isFile()) {
+        boolean uptodate = true
+        def modified = timestampFile.lastModified()
+        project(':core').fileTree('src/main').visit {fte ->
+            if (fte.file.isFile() && fte.lastModified > modified) {
+                uptodate = false
+                fte.stopVisiting()
+            }
+        }
+        if (!uptodate) {
+            timestampFile.setLastModified(new Date().time)
+        }
+    } else {
+        timestampFile.parentFile.mkdirs()
+        timestampFile.createNewFile()
+    }
+
+    ext.buildTime = new Date(timestampFile.lastModified())
+    ext.buildTimestamp = timestampFormat.format(buildTime)
+}
+
+ext.isSnapshot = !project.hasProperty("notSnapshot")
+
+if (version == "unspecified") {
+    if (!isSnapshot) {
+        throw new InvalidUserDataException("Cannot specify 'notSnapshot' without specifying 'version'")
+    }
+
+    // 0.0 means the version is non symbolic.
+    // We have to use something that looks like a real version number because there are a few places
+    // where this value is validated, e.g. the wrapper stuff. We should fix this at some point so we can do
+    // things like use build numbers or commit IDs.
+    //
+    // version = "0.0"
+
+    // Using 0.0 causes issues with the Tooling API. It's something to do with older versions of Gradle not
+    // understanding the 0.0 version number scheme. For the time being, we are going to hardcode 1.1.
+    version = "1.1"
+
+    ext.isSymbolicVersion = false
+} else {
+    ext.isSymbolicVersion = true
+}
+
+if (isSnapshot) {
+    version += "-$buildTimestamp"
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d9cd04d..7723e1e 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Mar 30 20:19:42 MDT 2012
+#Thu May 17 15:08:14 BST 2012
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.0-rc-1-20120331034014+0200-bin.zip
+distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.1-20120723222958+0000-bin.zip
diff --git a/gradlew b/gradlew
index 569f6ed..bafc554 100755
--- a/gradlew
+++ b/gradlew
@@ -107,7 +107,7 @@ fi
 
 # For Darwin, add options to specify how the application appears in the dock
 if $darwin; then
-    JAVA_OPTS="$JAVA_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
 fi
 
 # For Cygwin, switch paths to Windows format before running java
diff --git a/settings.gradle b/settings.gradle
index 2e16d21..bf63d06 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -19,6 +19,7 @@ include 'coreImpl'
 include 'wrapper'
 include 'cli'
 include 'launcher'
+include 'messaging'
 include 'plugins'
 include 'scala'
 include 'ide'
@@ -40,8 +41,9 @@ include 'ear'
 include 'native'
 include 'internalTesting'
 include 'internalIntegTesting'
-include 'website'
 include 'performance'
+include 'javascript'
+include 'migration'
 
 rootProject.name = 'gradle'
 rootProject.children.each {project ->
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
index 1804945..4905ab0 100755
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/DefaultAnnouncerFactory.groovy
@@ -16,11 +16,12 @@
 package org.gradle.api.plugins.announce.internal
 
 import org.gradle.api.InvalidUserDataException
+import org.gradle.api.JavaVersion
+import org.gradle.api.internal.ProcessOperations
 import org.gradle.api.plugins.announce.AnnouncePluginExtension
 import org.gradle.api.plugins.announce.Announcer
-import org.gradle.internal.os.OperatingSystem
 import org.gradle.internal.jvm.Jvm
-import org.gradle.api.internal.ProcessOperations
+import org.gradle.internal.os.OperatingSystem
 
 /**
  * @author Hans Dockter
@@ -60,7 +61,7 @@ class DefaultAnnouncerFactory implements AnnouncerFactory {
             case "snarl":
                 return new Snarl(iconProvider)
             case "growl":
-                if (Jvm.current().java6Compatible && Jvm.current().supportsAppleScript) {
+                if (JavaVersion.current().java6Compatible && Jvm.current().supportsAppleScript) {
                     try {
                         return getClass().getClassLoader().loadClass("org.gradle.api.plugins.announce.internal.jdk6.AppleScriptBackedGrowlAnnouncer").newInstance(iconProvider)
                     } catch (ClassNotFoundException e) {
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/Action.java b/subprojects/base-services/src/main/java/org/gradle/api/Action.java
new file mode 100644
index 0000000..b7e60b2
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/Action.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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 org.gradle.api;
+
+/**
+ * Performs some action against objects of type T.
+ *
+ * @param <T> The type of object which this action accepts. 
+ */
+public interface Action<T> {
+    /**
+     * Performs this action against the given object.
+     *
+     * @param t The object to perform the action on.
+     */
+    void execute(T t);
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/Experimental.java b/subprojects/base-services/src/main/java/org/gradle/api/Experimental.java
new file mode 100644
index 0000000..7e0a2a4
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/Experimental.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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 org.gradle.api;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates that a feature is experimental. This means that the feature is currently a work-in-progress and may
+ * change at any time.
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface Experimental {
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/JavaVersion.java b/subprojects/base-services/src/main/java/org/gradle/api/JavaVersion.java
new file mode 100644
index 0000000..814f643
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/JavaVersion.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2009 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 org.gradle.api;
+
+import org.gradle.internal.SystemProperties;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An enumeration of Java versions.
+ */
+public enum JavaVersion {
+    VERSION_1_1(false), VERSION_1_2(false), VERSION_1_3(false), VERSION_1_4(false), VERSION_1_5(true), VERSION_1_6(true), VERSION_1_7(true), VERSION_1_8(true);
+
+    private final boolean hasMajorVersion;
+
+    private JavaVersion(boolean hasMajorVersion) {
+        this.hasMajorVersion = hasMajorVersion;
+    }
+
+    /**
+     * Converts the given object into a {@code JavaVersion}.
+     *
+     * @param value An object whose toString() value is to be converted. May be null.
+     * @return The version, or null if the provided value is null.
+     * @throws IllegalArgumentException when the provided value cannot be converted.
+     */
+    public static JavaVersion toVersion(Object value) throws IllegalArgumentException {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof JavaVersion) {
+            return (JavaVersion) value;
+        }
+
+        String name = value.toString();
+        if (name.matches("\\d")) {
+            int index = Integer.parseInt(name) - 1;
+            if (index < values().length && values()[index].hasMajorVersion) {
+                return values()[index];
+            }
+        }
+
+        Matcher matcher = Pattern.compile("1\\.(\\d)(\\D.*)?").matcher(name);
+        if (matcher.matches()) {
+            return values()[Integer.parseInt(matcher.group(1)) - 1];
+        }
+        throw new IllegalArgumentException(String.format("Could not determine java version from '%s'.", name));
+    }
+
+    /**
+     * Returns the version of the current JVM.
+     *
+     * @return The version of the current JVM.
+     */
+    public static JavaVersion current() {
+        return toVersion(SystemProperties.getJavaVersion());
+    }
+
+    public boolean isJava5() {
+        return this == VERSION_1_5;
+    }
+
+    public boolean isJava6() {
+        return this == VERSION_1_6;
+    }
+
+    public boolean isJava7() {
+        return this == VERSION_1_7;
+    }
+
+    private boolean isJava8() {
+        return this == VERSION_1_8;
+    }
+
+    public boolean isJava5Compatible() {
+        return isJava5() || isJava6Compatible();
+    }
+
+    public boolean isJava6Compatible() {
+        return isJava6() || isJava7Compatible();
+    }
+
+    public boolean isJava7Compatible() {
+        return isJava7() || isJava8Compatible();
+    }
+
+    public boolean isJava8Compatible() {
+        return isJava8();
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+
+    private String getName() {
+        return name().substring("VERSION_".length()).replace('_', '.');
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/Nullable.java b/subprojects/base-services/src/main/java/org/gradle/api/Nullable.java
new file mode 100644
index 0000000..e53bf54
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/api/Nullable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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 org.gradle.api;
+
+import java.lang.annotation.*;
+
+/**
+ * Indicates that the value of an element can be null.
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
+public @interface Nullable {}
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java
index 77ccb4c..0e4242e 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/Factory.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal;
 
 /**
- * @deprecated This is here because Groovy tasks compiled against older versions have this type baked into their byte-code, and cannot be loaded if it's not found.
+ * @deprecated This is here because tasks implemented in Groovy that are compiled against older versions of Gradle have this type baked into their byte-code, and cannot be loaded if it's not found.
  */
 @Deprecated
 public interface Factory<T> extends org.gradle.internal.Factory<T> {
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java
index 05a3cce..92825aa 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/project/ServiceRegistry.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.project;
 
 /**
- * @deprecated This is here because Groovy tasks compiled against older versions have this type baked into their byte-code, and cannot be loaded if it's not found.
+ * @deprecated This is here because tasks implemented in Groovy that are compiled against older versions of Gradle have this type baked into their byte-code, and cannot be loaded if it's not found.
  */
 @Deprecated
 public interface ServiceRegistry extends org.gradle.internal.service.ServiceRegistry {
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/TimeProvider.java b/subprojects/base-services/src/main/java/org/gradle/internal/TimeProvider.java
new file mode 100644
index 0000000..dcc104a
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/TimeProvider.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2009 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 org.gradle.internal;
+
+public interface TimeProvider {
+
+    long getCurrentTime();
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/TrueTimeProvider.java b/subprojects/base-services/src/main/java/org/gradle/internal/TrueTimeProvider.java
new file mode 100644
index 0000000..4e71b8e
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/TrueTimeProvider.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2009 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 org.gradle.internal;
+
+public class TrueTimeProvider implements TimeProvider {
+
+    public long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classpath/ClassPath.java b/subprojects/base-services/src/main/java/org/gradle/internal/classpath/ClassPath.java
new file mode 100644
index 0000000..ab65707
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classpath/ClassPath.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.classpath;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+
+/**
+ * An immutable classpath.
+ */
+public interface ClassPath {
+    boolean isEmpty();
+
+    Collection<URI> getAsURIs();
+
+    Collection<File> getAsFiles();
+
+    Collection<URL> getAsURLs();
+
+    URL[] getAsURLArray();
+
+    ClassPath plus(Collection<File> classPath);
+    
+    ClassPath plus(ClassPath classPath);
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/classpath/DefaultClassPath.java b/subprojects/base-services/src/main/java/org/gradle/internal/classpath/DefaultClassPath.java
new file mode 100644
index 0000000..d644701
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/classpath/DefaultClassPath.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.classpath;
+
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An immutable classpath.
+ */
+public class DefaultClassPath implements ClassPath, Serializable {
+    private final List<File> files;
+
+    public DefaultClassPath(Iterable<File> files) {
+        this.files = new ArrayList<File>();
+        for (File file : files) {
+            this.files.add(file);
+        }
+    }
+    
+    public DefaultClassPath(File... files) {
+        this(Arrays.asList(files));
+    }
+
+    public boolean isEmpty() {
+        return files.isEmpty();
+    }
+
+    public Collection<URI> getAsURIs() {
+        List<URI> urls = new ArrayList<URI>();
+        for (File file : files) {
+            urls.add(file.toURI());
+        }
+        return urls;
+    }
+
+    public Collection<File> getAsFiles() {
+        return files;
+    }
+
+    public URL[] getAsURLArray() {
+        Collection<URL> result = getAsURLs();
+        return result.toArray(new URL[result.size()]);
+    }
+
+    public Collection<URL> getAsURLs() {
+        List<URL> urls = new ArrayList<URL>();
+        for (File file : files) {
+            try {
+                urls.add(file.toURI().toURL());
+            } catch (MalformedURLException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+        return urls;
+    }
+
+    public ClassPath plus(ClassPath other) {
+        if (files.isEmpty()) {
+            return other;
+        }
+        if (other.isEmpty()) {
+            return this;
+        }
+        return new DefaultClassPath(concat(files, other.getAsFiles()));
+    }
+
+    public ClassPath plus(Collection<File> other) {
+        if (other.isEmpty()) {
+            return this;
+        }
+        return new DefaultClassPath(concat(files, other));
+    }
+
+    private Iterable<File> concat(List<File> files1, Collection<File> files2) {
+        List<File> result = new ArrayList<File>();
+        result.addAll(files1);
+        result.addAll(files2);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        DefaultClassPath other = (DefaultClassPath) obj;
+        return files.equals(other.files);
+    }
+
+    @Override
+    public int hashCode() {
+        return files.hashCode();
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/AsyncStoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/AsyncStoppable.java
new file mode 100755
index 0000000..7eb8cae
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/AsyncStoppable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 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 org.gradle.internal.concurrent;
+
+import org.gradle.internal.Stoppable;
+
+/**
+ * A {@link Stoppable} object whose stop process can be performed asynchronously.
+ */
+public interface AsyncStoppable extends Stoppable {
+    /**
+     * <p>Requests that this stoppable commence a graceful stop. Does not block. You should call {@link
+     * org.gradle.internal.Stoppable#stop} to wait for the stop process to complete.</p>
+     *
+     * <p>Generally, an {@code AsyncStoppable} should continue to complete existing work after this method has returned.
+     * It should, however, stop accepting new work.</p>
+     *
+     * <p>
+     * Requesting stopping does not guarantee the stoppable actually stops.
+     * Requesting stopping means preparing for stopping; stopping accepting new work.
+     * You have to call stop at some point anyway if your intention is to completely stop the stoppable.
+     */
+    void requestStop();
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/DefaultExecutorFactory.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/DefaultExecutorFactory.java
new file mode 100644
index 0000000..508e28d
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/DefaultExecutorFactory.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2010 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 org.gradle.internal.concurrent;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class DefaultExecutorFactory implements ExecutorFactory, Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExecutorFactory.class);
+    private final Set<StoppableExecutorImpl> executors = new CopyOnWriteArraySet<StoppableExecutorImpl>();
+
+    public void stop() {
+        try {
+            new CompositeStoppable(executors).stop();
+        } finally {
+            executors.clear();
+        }
+    }
+
+    public StoppableExecutor create(String displayName) {
+        StoppableExecutorImpl executor = new StoppableExecutorImpl(createExecutor(displayName));
+        executors.add(executor);
+        return executor;
+    }
+
+    protected ExecutorService createExecutor(String displayName) {
+        return Executors.newCachedThreadPool(new ThreadFactoryImpl(displayName));
+    }
+
+    private class StoppableExecutorImpl implements StoppableExecutor {
+        private final ExecutorService executor;
+        private final ThreadLocal<Runnable> executing = new ThreadLocal<Runnable>();
+        private final AtomicReference<Throwable> failure = new AtomicReference<Throwable>();
+
+        public StoppableExecutorImpl(ExecutorService executor) {
+            this.executor = executor;
+        }
+
+        public void execute(final Runnable command) {
+            executor.execute(new Runnable() {
+                public void run() {
+                    executing.set(command);
+                    try {
+                        command.run();
+                    } catch (Throwable throwable) {
+                        if (!failure.compareAndSet(null, throwable)) {
+                            LOGGER.error(String.format("Failed to execute %s.", command), throwable);
+                        }
+                    } finally {
+                        executing.set(null);
+                    }
+                }
+            });
+        }
+
+        public void requestStop() {
+            executor.shutdown();
+        }
+
+        public void stop() {
+            stop(Integer.MAX_VALUE, TimeUnit.SECONDS);
+        }
+
+        public void stop(int timeoutValue, TimeUnit timeoutUnits) throws IllegalStateException {
+            requestStop();
+            if (executing.get() != null) {
+                throw new IllegalStateException("Cannot stop this executor from an executor thread.");
+            }
+            try {
+                try {
+                    if (!executor.awaitTermination(timeoutValue, timeoutUnits)) {
+                        executor.shutdownNow();
+                        throw new IllegalStateException("Timeout waiting for concurrent jobs to complete.");
+                    }
+                } catch (InterruptedException e) {
+                    throw new UncheckedException(e);
+                }
+                if (failure.get() != null) {
+                    throw UncheckedException.throwAsUncheckedException(failure.get());
+                }
+            } finally {
+                executors.remove(this);
+            }
+        }
+    }
+
+    private static class ThreadFactoryImpl implements ThreadFactory {
+        private final AtomicLong counter = new AtomicLong();
+        private final String displayName;
+
+        public ThreadFactoryImpl(String displayName) {
+            this.displayName = displayName;
+        }
+
+        public Thread newThread(Runnable r) {
+            Thread thread = new Thread(r);
+            long count = counter.incrementAndGet();
+            if (count == 1) {
+                thread.setName(displayName);
+            } else {
+                thread.setName(String.format("%s Thread %s", displayName, count));
+            }
+            return thread;
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ExecutorFactory.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ExecutorFactory.java
new file mode 100644
index 0000000..32ddb9e
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/ExecutorFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 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 org.gradle.internal.concurrent;
+
+public interface ExecutorFactory {
+    /**
+     * Creates an executor which can run multiple tasks concurrently. It is the caller's responsibility to stop the executor.
+     *
+     * @param displayName The display name for the this executor. Used for thread names, logging and error message.
+     * @return The executor.
+     */
+    StoppableExecutor create(String displayName);
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/StoppableExecutor.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/StoppableExecutor.java
new file mode 100644
index 0000000..d8a6815
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/StoppableExecutor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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 org.gradle.internal.concurrent;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+public interface StoppableExecutor extends AsyncStoppable, Executor {
+    /**
+     * Stops accepting new jobs and blocks until all currently executing jobs have been completed.
+     */
+    void stop();
+
+    /**
+     * Stops accepting new jobs and blocks until all currently executing jobs have been completed. Once the given
+     * timeout has been reached, forcefully stops remaining jobs and throws an exception.
+     *
+     * @throws IllegalStateException on timeout.
+     */
+    void stop(int timeoutValue, TimeUnit timeoutUnits) throws IllegalStateException;
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/Synchronizer.java b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/Synchronizer.java
new file mode 100644
index 0000000..4d2c419
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/concurrent/Synchronizer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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 org.gradle.internal.concurrent;
+
+import org.gradle.internal.Factory;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class Synchronizer {
+
+    private final Lock lock = new ReentrantLock();
+
+    public <T> T synchronize(Factory<T> factory) {
+        lock.lock();
+        try {
+            return factory.create();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void synchronize(Runnable operation) {
+        lock.lock();
+        try {
+            operation.run();
+        } finally {
+            lock.unlock();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/id/CompositeIdGenerator.java b/subprojects/base-services/src/main/java/org/gradle/internal/id/CompositeIdGenerator.java
new file mode 100644
index 0000000..becd95a
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/id/CompositeIdGenerator.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.id;
+
+import java.io.Serializable;
+
+public class CompositeIdGenerator implements IdGenerator<Object> {
+    private final Object scope;
+    private final IdGenerator<?> generator;
+
+    public CompositeIdGenerator(Object scope, IdGenerator<?> generator) {
+        this.scope = scope;
+        this.generator = generator;
+    }
+
+    public Object generateId() {
+        return new CompositeId(scope, generator.generateId());
+    }
+    
+    private static class CompositeId implements Serializable {
+        private final Object scope;
+        private final Object id;
+
+        private CompositeId(Object scope, Object id) {
+            this.id = id;
+            this.scope = scope;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null || o.getClass() != getClass()) {
+                return false;
+            }
+
+            CompositeId other = (CompositeId) o;
+            return other.id.equals(id) && other.scope.equals(scope);
+        }
+
+        @Override
+        public int hashCode() {
+            return scope.hashCode() ^ id.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s.%s", scope, id);
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/id/IdGenerator.java b/subprojects/base-services/src/main/java/org/gradle/internal/id/IdGenerator.java
new file mode 100644
index 0000000..b3e92e7
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/id/IdGenerator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.id;
+
+/**
+ * Generates a sequence of unique ids of type T. Implementations must be thread-safe.
+ */
+public interface IdGenerator<T> {
+    /**
+     * Generates a new id. Values must be serializable.
+     *
+     * @return The id. Must not return null. Must not return a given value more than once.
+     */
+    T generateId();
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/id/LongIdGenerator.java b/subprojects/base-services/src/main/java/org/gradle/internal/id/LongIdGenerator.java
new file mode 100644
index 0000000..5791d3f
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/id/LongIdGenerator.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.id;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+public class LongIdGenerator implements IdGenerator<Long> {
+    private final AtomicLong nextId = new AtomicLong(1);
+
+    public Long generateId() {
+        return nextId.getAndIncrement();
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/id/RandomLongIdGenerator.java b/subprojects/base-services/src/main/java/org/gradle/internal/id/RandomLongIdGenerator.java
new file mode 100644
index 0000000..3a999cc
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/id/RandomLongIdGenerator.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.id;
+
+import java.util.Random;
+
+public class RandomLongIdGenerator implements IdGenerator<Long> {
+    private final Random random = new Random();
+
+    public Long generateId() {
+        return random.nextLong();
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/id/UUIDGenerator.java b/subprojects/base-services/src/main/java/org/gradle/internal/id/UUIDGenerator.java
new file mode 100644
index 0000000..a45524f
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/id/UUIDGenerator.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.id;
+
+import java.util.UUID;
+
+public class UUIDGenerator implements IdGenerator<UUID> {
+    public UUID generateId() {
+        return UUID.randomUUID();
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/io/ClassLoaderObjectInputStream.java b/subprojects/base-services/src/main/java/org/gradle/internal/io/ClassLoaderObjectInputStream.java
new file mode 100644
index 0000000..d74aed8
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/io/ClassLoaderObjectInputStream.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010 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 org.gradle.internal.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+
+public class ClassLoaderObjectInputStream extends ObjectInputStream {
+    private final ClassLoader loader;
+
+    public ClassLoaderObjectInputStream(InputStream in, ClassLoader loader) throws IOException {
+        super(in);
+        this.loader = loader;
+    }
+
+    public ClassLoader getClassLoader() {
+        return loader;
+    }
+
+    @Override
+    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+        try {
+            return loader.loadClass(desc.getName());
+        } catch (ClassNotFoundException e) {
+            return super.resolveClass(desc);
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java
index 8d4c436..b2800a4 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/jvm/Jvm.java
@@ -16,6 +16,7 @@
 
 package org.gradle.internal.jvm;
 
+import org.gradle.api.JavaVersion;
 import org.gradle.internal.SystemProperties;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.os.OperatingSystem;
@@ -37,6 +38,7 @@ public class Jvm implements JavaInfo {
     //discovered java location
     private final File javaHome;
     private final boolean userSupplied;
+    private final JavaVersion javaVersion;
 
     public static Jvm current() {
         return create(null);
@@ -71,12 +73,14 @@ public class Jvm implements JavaInfo {
                 throw new UncheckedException(e);
             }
             this.javaHome = findJavaHome(javaBase);
+            this.javaVersion = JavaVersion.current();
             this.userSupplied = false;
         } else {
             //precisely use what the user wants and validate strictly further on
             this.javaBase = suppliedJavaBase;
             this.javaHome = suppliedJavaBase;
             this.userSupplied = true;
+            this.javaVersion = null;
         }
     }
     
@@ -153,24 +157,11 @@ public class Jvm implements JavaInfo {
         return findExecutable(name);
     }
 
-    public boolean isJava5() {
-        return SystemProperties.getJavaVersion().startsWith("1.5");    
-    }
-
-    public boolean isJava6() {
-        return SystemProperties.getJavaVersion().startsWith("1.6");
-    }
-
-    public boolean isJava7() {
-        return SystemProperties.getJavaVersion().startsWith("1.7");
-    }
-
-    public boolean isJava5Compatible() {
-         return isJava5() || isJava6Compatible();
-    }
-
-    public boolean isJava6Compatible() {
-        return isJava6() || isJava7();
+    /**
+     * @return the {@link JavaVersion} information
+     */
+    public JavaVersion getJavaVersion() {
+        return javaVersion;
     }
 
     /**
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/DirectInstantiator.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/DirectInstantiator.java
new file mode 100644
index 0000000..6864722
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/DirectInstantiator.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.reflect;
+
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class DirectInstantiator implements Instantiator {
+    public <T> T newInstance(Class<T> type, Object... params) {
+        try {
+            List<Constructor<?>> matches = new ArrayList<Constructor<?>>();
+            for (Constructor<?> constructor : type.getConstructors()) {
+                if (isMatch(constructor, params)) {
+                    matches.add(constructor);
+                }
+            }
+            if (matches.isEmpty()) {
+                throw new IllegalArgumentException(String.format("Could not find any public constructor for %s which accepts parameters %s.", type, Arrays.toString(params)));
+            }
+            if (matches.size() > 1) {
+                throw new IllegalArgumentException(String.format("Found multiple public constructors for %s which accept parameters %s.", type, Arrays.toString(params)));
+            }
+            return type.cast(matches.get(0).newInstance(params));
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.throwAsUncheckedException(e.getCause());
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private boolean isMatch(Constructor<?> constructor, Object... params) {
+        if (constructor.getParameterTypes().length != params.length) {
+            return false;
+        }
+        for (int i = 0; i < params.length; i++) {
+            Object param = params[i];
+            Class<?> parameterType = constructor.getParameterTypes()[i];
+            if (parameterType.isPrimitive()) {
+                if (!JavaReflectionUtil.getWrapperTypeForPrimitiveType(parameterType).isInstance(param)) {
+                    return false;
+                }
+            } else {
+                if (param != null && !parameterType.isInstance(param)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/Instantiator.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/Instantiator.java
new file mode 100644
index 0000000..e00aa04
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/Instantiator.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.reflect;
+
+/**
+ * An object that can create new instances of a given type, which may be decorated in some fashion.
+ */
+public interface Instantiator {
+
+    /**
+     * Create a new instance of T, using {@code parameters} as the construction parameters.
+     */
+    <T> T newInstance(Class<T> type, Object... parameters);
+
+}
\ No newline at end of file
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaReflectionUtil.java b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaReflectionUtil.java
new file mode 100644
index 0000000..215786c
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/reflect/JavaReflectionUtil.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.reflect;
+
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.Method;
+
+/**
+ * Simple implementations of some reflection capabilities. In contrast to org.gradle.util.ReflectionUtil,
+ * this class doesn't make use of Groovy.
+ */
+public class JavaReflectionUtil {
+    public static Object readProperty(Object target, String property) {
+        try {
+            Method getterMethod;
+            try {
+                getterMethod = target.getClass().getMethod(toMethodName("get", property));
+            } catch (NoSuchMethodException e) {
+                try {
+                    getterMethod = target.getClass().getMethod(toMethodName("is", property));
+                } catch (NoSuchMethodException e2) {
+                    throw e;
+                }
+            }
+            return getterMethod.invoke(target);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public static void writeProperty(Object target, String property, Object value) {
+        try {
+            String setterName = toMethodName("set", property);
+            for (Method method: target.getClass().getMethods()) {
+                if (!method.getName().equals(setterName)) { continue; }
+                if (method.getParameterTypes().length != 1) { continue; }
+                method.invoke(target, value);
+                return;
+            }
+            throw new NoSuchMethodException(String.format("could not find setter method '%s'", setterName));
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private static String toMethodName(String prefix, String propertyName) {
+        return prefix + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
+    }
+
+    public static Class<?> getWrapperTypeForPrimitiveType(Class<?> type) {
+        if (type == Boolean.TYPE) {
+            return Boolean.class;
+        } else if (type == Long.TYPE) {
+            return Long.class;
+        } else if (type == Integer.TYPE) {
+            return Integer.class;
+        } else if (type == Short.TYPE) {
+            return Short.class;
+        } else if (type == Byte.TYPE) {
+            return Byte.class;
+        } else if (type == Float.TYPE) {
+            return Float.class;
+        } else if (type == Double.TYPE) {
+            return Double.class;
+        }
+        throw new IllegalArgumentException(String.format("Don't know how wrapper type for primitive type %s.", type));
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/DefaultServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/DefaultServiceRegistry.java
index 70520d8..a5cdde3 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/service/DefaultServiceRegistry.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/DefaultServiceRegistry.java
@@ -100,7 +100,7 @@ public class DefaultServiceRegistry implements ServiceRegistry {
                     && method.getReturnType() != Void.class
                     && method.getParameterTypes()[0].equals(method.getReturnType())) {
                 if (parent == null) {
-                    throw new IllegalArgumentException("Cannot use decorator methods when no parent registry is provided.");
+                    throw new ServiceLookupException("Cannot use decorator methods when no parent registry is provided.");
                 }
                 if (decoratorMethods.add(method.getName())) {
                     ownServices.add(new DecoratorMethodService(method));
@@ -205,7 +205,7 @@ public class DefaultServiceRegistry implements ServiceRegistry {
                 Factory<T> factory = provider.getFactory(type);
                 if (factory != null) {
                     if (match != null) {
-                        throw new IllegalArgumentException(String.format("Multiple factories for objects of type %s available in %s.", type.getSimpleName(), DefaultServiceRegistry.this.toString()));
+                        throw new ServiceLookupException(String.format("Multiple factories for objects of type %s available in %s.", type.getSimpleName(), DefaultServiceRegistry.this.toString()));
                     }
                     match = factory;
                 }
@@ -219,7 +219,7 @@ public class DefaultServiceRegistry implements ServiceRegistry {
                 T service = provider.getService(serviceType);
                 if (service != null) {
                     if (match != null) {
-                        throw new IllegalArgumentException(String.format("Multiple services of type %s available in %s.", serviceType.getSimpleName(), DefaultServiceRegistry.this.toString()));
+                        throw new ServiceLookupException(String.format("Multiple services of type %s available in %s.", serviceType.getSimpleName(), DefaultServiceRegistry.this.toString()));
                     }
                     match = service;
                 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLocator.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLocator.java
new file mode 100644
index 0000000..30232c7
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLocator.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.service;
+
+import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Uses the Jar service resource specification to locate service implementations.
+ */
+public class ServiceLocator implements ServiceRegistry {
+    private final ClassLoader classLoader;
+    private final Map<Class<?>, Object> implementations = new ConcurrentHashMap<Class<?>, Object>();
+
+    public ServiceLocator(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public <T> T get(Class<T> serviceType) throws UnknownServiceException {
+        synchronized (implementations) {
+            T implementation = serviceType.cast(implementations.get(serviceType));
+            if (implementation == null) {
+                implementation = getFactory(serviceType).create();
+                implementations.put(serviceType, implementation);
+            }
+            return implementation;
+        }
+    }
+
+    public <T> ServiceFactory<T> getFactory(final Class<T> serviceType) throws UnknownServiceException {
+        ServiceFactory<T> factory = findFactory(serviceType);
+        if (factory == null) {
+            throw new UnknownServiceException(serviceType, String.format("Could not find meta-data resource 'META-INF/services/%s' for service '%s'.", serviceType.getName(), serviceType.getName()));
+        }
+        return factory;
+    }
+
+    /**
+     * Locates a factory for a given service. Returns null when no service implementation is available.
+     */
+    public <T> ServiceFactory<T> findFactory(Class<T> serviceType) {
+        Class<? extends T> implementationClass = findServiceImplementationClass(serviceType);
+        if (implementationClass == null) {
+            return null;
+        }
+        return new ServiceFactory<T>(serviceType, implementationClass);
+    }
+
+    public <T> T newInstance(Class<T> type) throws UnknownServiceException {
+        return getFactory(type).create();
+    }
+
+    <T> Class<? extends T> findServiceImplementationClass(Class<T> serviceType) {
+        String implementationClassName;
+        try {
+            implementationClassName = findServiceImplementationClassName(serviceType);
+        } catch (Exception e) {
+            throw new ServiceLookupException(String.format("Could not determine implementation class for service '%s'.", serviceType.getName()), e);
+        }
+        if (implementationClassName == null) {
+            return null;
+        }
+        try {
+            Class<?> implClass = classLoader.loadClass(implementationClassName);
+            if (!serviceType.isAssignableFrom(implClass)) {
+                throw new RuntimeException(String.format("Implementation class '%s' is not assignable to service class '%s'.", implementationClassName, serviceType.getName()));
+            }
+            return implClass.asSubclass(serviceType);
+        } catch (Throwable t) {
+            throw new ServiceLookupException(String.format("Could not load implementation class '%s' for service '%s'.", implementationClassName, serviceType.getName()), t);
+        }
+    }
+
+    private String findServiceImplementationClassName(Class<?> serviceType) throws IOException {
+        String resourceName = "META-INF/services/" + serviceType.getName();
+        URL resource = classLoader.getResource(resourceName);
+        if (resource == null) {
+            return null;
+        }
+
+        InputStream inputStream = resource.openStream();
+        try {
+            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                line = line.replaceAll("#.*", "").trim();
+                if (line.length() > 0) {
+                    return line;
+                }
+            }
+        } finally {
+            inputStream.close();
+        }
+        throw new RuntimeException(String.format("No implementation class for service '%s' specified in resource '%s'.", serviceType.getName(), resource));
+    }
+
+    public static class ServiceFactory<T> implements Factory<T> {
+        private final Class<T> serviceType;
+        private final Class<? extends T> implementationClass;
+
+        public ServiceFactory(Class<T> serviceType, Class<? extends T> implementationClass) {
+            this.serviceType = serviceType;
+            this.implementationClass = implementationClass;
+        }
+
+        public Class<? extends T> getImplementationClass() {
+            return implementationClass;
+        }
+
+        public T create() {
+            return newInstance();
+        }
+
+        public T newInstance(Object... params) {
+            Instantiator instantiator = new DirectInstantiator();
+            try {
+                return instantiator.newInstance(implementationClass, params);
+            } catch (Throwable t) {
+                throw new RuntimeException(String.format("Could not create an implementation of service '%s'.", serviceType.getName()), t);
+            }
+        }
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLookupException.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLookupException.java
new file mode 100644
index 0000000..e30fb0b
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceLookupException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.service;
+
+/**
+ * Thrown when there is some failure locating a service.
+ */
+public class ServiceLookupException extends RuntimeException {
+    public ServiceLookupException(String message) {
+        super(message);
+    }
+
+    public ServiceLookupException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java
index 3b255a2..2db554d 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java
@@ -27,9 +27,10 @@ public interface ServiceRegistry {
      * @param serviceType The service type.
      * @param <T>         The service type.
      * @return The service instance. Never returns null.
-     * @throws org.gradle.internal.service.UnknownServiceException When there is no service of the given type available.
+     * @throws UnknownServiceException When there is no service of the given type available.
+     * @throws ServiceLookupException On failure to lookup the specified service.
      */
-    <T> T get(Class<T> serviceType) throws UnknownServiceException;
+    <T> T get(Class<T> serviceType) throws UnknownServiceException, ServiceLookupException;
 
     /**
      * Locates a factory which can create services of the given type.
@@ -38,8 +39,9 @@ public interface ServiceRegistry {
      * @param <T>  The service type that the factory should create.
      * @return The factory. Never returns null.
      * @throws UnknownServiceException When there is no factory available for services of the given type.
+     * @throws ServiceLookupException On failure to lookup the specified service factory.
      */
-    <T> Factory<T> getFactory(Class<T> type) throws UnknownServiceException;
+    <T> Factory<T> getFactory(Class<T> type) throws UnknownServiceException, ServiceLookupException;
 
     /**
      * Creates a new service instance of the given type.
@@ -48,6 +50,7 @@ public interface ServiceRegistry {
      * @param <T>  The service type.
      * @return The instance. Never returns null.
      * @throws UnknownServiceException When there is no factory available for services of the given type.
+     * @throws ServiceLookupException On failure to lookup the specified service factory.
      */
-    <T> T newInstance(Class<T> type) throws UnknownServiceException;
+    <T> T newInstance(Class<T> type) throws UnknownServiceException, ServiceLookupException;
 }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/SynchronizedServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/SynchronizedServiceRegistry.java
new file mode 100644
index 0000000..d59c88b
--- /dev/null
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/SynchronizedServiceRegistry.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2011 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 org.gradle.internal.service;
+
+import org.gradle.internal.Factory;
+import org.gradle.internal.concurrent.Synchronizer;
+
+/**
+ * by Szczepan Faber, created at: 11/24/11
+ */
+public class SynchronizedServiceRegistry implements ServiceRegistry {
+    private final Synchronizer synchronizer = new Synchronizer();
+    private final ServiceRegistry delegate;
+
+    public SynchronizedServiceRegistry(ServiceRegistry delegate) {
+        this.delegate = delegate;
+    }
+
+    public <T> T get(final Class<T> serviceType) throws UnknownServiceException {
+        return synchronizer.synchronize(new Factory<T>() {
+            public T create() {
+                return delegate.get(serviceType);
+            }
+        });
+    }
+
+    public <T> Factory<T> getFactory(final Class<T> type) throws UnknownServiceException {
+        return synchronizer.synchronize(new Factory<Factory<T>>() {
+            public Factory<T> create() {
+                return new SynchronizedFactory<T>(delegate.getFactory(type));
+            }
+        });
+    }
+
+    public <T> T newInstance(final Class<T> type) throws UnknownServiceException {
+        return synchronizer.synchronize(new Factory<T>() {
+            public T create() {
+                return delegate.newInstance(type);
+            }
+        });
+    }
+
+    private class SynchronizedFactory<T> implements Factory<T> {
+        private final Factory<T> delegateFactory;
+
+        public SynchronizedFactory(Factory<T> delegateFactory) {
+            this.delegateFactory = delegateFactory;
+        }
+
+        public T create() {
+            return synchronizer.synchronize(delegateFactory);
+        }
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/JavaVersionSpec.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/JavaVersionSpec.groovy
new file mode 100644
index 0000000..be57cf3
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/JavaVersionSpec.groovy
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2009 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 org.gradle.api;
+
+
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Specification
+
+public class JavaVersionSpec extends Specification {
+
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+    
+    def toStringReturnsVersion() {
+        expect:
+        JavaVersion.VERSION_1_3.toString() == "1.3"
+        JavaVersion.VERSION_1_4.toString() == "1.4"
+        JavaVersion.VERSION_1_5.toString() == "1.5"
+        JavaVersion.VERSION_1_6.toString() == "1.6"
+        JavaVersion.VERSION_1_7.toString() == "1.7"
+    }
+
+    def convertsStringToVersion() {
+        expect:
+        JavaVersion.toVersion("1.3") == JavaVersion.VERSION_1_3
+        JavaVersion.toVersion("1.5") == JavaVersion.VERSION_1_5
+        JavaVersion.toVersion("1.5.4") == JavaVersion.VERSION_1_5
+        JavaVersion.toVersion("1.5_4") == JavaVersion.VERSION_1_5
+        JavaVersion.toVersion("1.5.0.4_b109") == JavaVersion.VERSION_1_5
+
+        JavaVersion.toVersion("5") == JavaVersion.VERSION_1_5
+        JavaVersion.toVersion("6") == JavaVersion.VERSION_1_6
+        JavaVersion.toVersion("7") == JavaVersion.VERSION_1_7
+        JavaVersion.toVersion("8") == JavaVersion.VERSION_1_8
+    }
+
+    def failsToConvertStringToVersionForUnknownVersion() {
+        expect:
+        conversionFails("1");
+        conversionFails("2");
+
+        conversionFails("17");
+
+        conversionFails("a");
+        conversionFails("");
+        conversionFails("  ");
+
+        conversionFails("1.54");
+        conversionFails("1.10");
+        conversionFails("2.0");
+        conversionFails("1_4");
+    }
+
+    def convertsVersionToVersion() {
+        expect:
+        JavaVersion.toVersion(JavaVersion.VERSION_1_4) == JavaVersion.VERSION_1_4
+    }
+
+    def convertsNumberToVersion() {
+        expect:
+        JavaVersion.toVersion(1.3) == JavaVersion.VERSION_1_3
+        JavaVersion.toVersion(1.5) == JavaVersion.VERSION_1_5
+        JavaVersion.toVersion(5) == JavaVersion.VERSION_1_5
+        JavaVersion.toVersion(6) == JavaVersion.VERSION_1_6
+        JavaVersion.toVersion(7) == JavaVersion.VERSION_1_7
+        JavaVersion.toVersion(1.7) == JavaVersion.VERSION_1_7
+        JavaVersion.toVersion(1.8) == JavaVersion.VERSION_1_8
+    }
+    
+    def failsToConvertNumberToVersionForUnknownVersion() {
+        expect:
+        conversionFails(1);
+        conversionFails(2);
+        conversionFails(17);
+        conversionFails(1.21);
+        conversionFails(2.0);
+        conversionFails(4.2);
+    }
+    
+    def currentReturnsJvmVersion() {
+        expect:
+        JavaVersion.current() == JavaVersion.toVersion(System.getProperty("java.version"))
+    }
+
+    def convertsNullToNull() {
+        expect:
+        JavaVersion.toVersion(null) == null
+    }
+
+    private void conversionFails(Object value) {
+        try {
+            JavaVersion.toVersion(value);
+            org.junit.Assert.fail();
+        } catch (IllegalArgumentException e) {
+            assert e.getMessage() == "Could not determine java version from '" + value + "'."
+        }
+    }
+
+    def "uses system property to determine if compatible with Java 5"() {
+        System.properties['java.version'] = '1.5'
+
+        expect:
+        JavaVersion.current().java5
+        !JavaVersion.current().java6
+        !JavaVersion.current().java7
+
+        and:
+        JavaVersion.current().java5Compatible
+        !JavaVersion.current().java6Compatible
+        !JavaVersion.current().java7Compatible
+    }
+
+    def "uses system property to determine if compatible with Java 6"() {
+        System.properties['java.version'] = '1.6'
+
+        expect:
+        !JavaVersion.current().java5
+        JavaVersion.current().java6
+        !JavaVersion.current().java7
+
+        and:
+        JavaVersion.current().java5Compatible
+        JavaVersion.current().java6Compatible
+        !JavaVersion.current().java7Compatible
+    }
+
+    def "uses system property to determine if compatible with Java 7"() {
+        System.properties['java.version'] = '1.7'
+
+        expect:
+        !JavaVersion.current().java5
+        !JavaVersion.current().java6
+        JavaVersion.current().java7
+
+        and:
+        JavaVersion.current().java5Compatible
+        JavaVersion.current().java6Compatible
+        JavaVersion.current().java7Compatible
+    }
+
+    def "uses system property to determine if compatible with Java 8"() {
+        System.properties['java.version'] = '1.8'
+
+        expect:
+        !JavaVersion.current().java5
+        !JavaVersion.current().java6
+        !JavaVersion.current().java7
+        JavaVersion.current().java8
+
+        and:
+        JavaVersion.current().java5Compatible
+        JavaVersion.current().java6Compatible
+        JavaVersion.current().java7Compatible
+        JavaVersion.current().java8Compatible
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/classpath/DefaultClassPathTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/classpath/DefaultClassPathTest.groovy
new file mode 100644
index 0000000..5d44518
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/classpath/DefaultClassPathTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.classpath
+
+import spock.lang.Specification
+import org.gradle.util.Matchers
+
+class DefaultClassPathTest extends Specification {
+    def "can add classpaths together"() {
+        def file1 = new File("a.jar")
+        def file2 = new File("b.jar")
+        def cp1 = new DefaultClassPath(file1)
+        def cp2 = new DefaultClassPath(file2)
+
+        expect:
+        def cp3 = cp1 + cp2
+        cp3.asFiles == [file1, file2]
+    }
+
+    def "add returns lhs when rhs is empty"() {
+        def cp1 = new DefaultClassPath(new File("a.jar"))
+        def cp2 = new DefaultClassPath()
+
+        expect:
+        (cp1 + cp2).is(cp1)
+    }
+
+    def "add returns rhs when lhs is empty"() {
+        def cp1 = new DefaultClassPath()
+        def cp2 = new DefaultClassPath(new File("a.jar"))
+
+        expect:
+        (cp1 + cp2).is(cp2)
+    }
+
+    def "can add collection of files to classpath"() {
+        def file1 = new File("a.jar")
+        def file2 = new File("a.jar")
+        def cp = new DefaultClassPath(file1)
+
+        expect:
+        (cp + [file2]).asFiles == [file1, file2]
+        (cp + []).is(cp)
+    }
+    
+    def "classpaths are equal when the contain the same sequence of files"() {
+        def file1 = new File("a.jar")
+        def file2 = new File("b.jar")
+        def file3 = new File("c.jar")
+        def cp = new DefaultClassPath(file1, file2)
+        def same = new DefaultClassPath(file1, file2)
+        def differentOrder = new DefaultClassPath(file2, file1)
+        def missing = new DefaultClassPath(file2)
+        def extra = new DefaultClassPath(file1, file2, file3)
+        def different = new DefaultClassPath(file3)
+
+        expect:
+        cp Matchers.strictlyEqual(same)
+        cp != differentOrder
+        cp != missing
+        cp != extra
+        cp != different
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactorySpec.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactorySpec.groovy
new file mode 100644
index 0000000..702a5b5
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactorySpec.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.concurrent
+
+import spock.lang.Specification
+
+class DefaultExecutorFactorySpec extends Specification {
+
+    def factory = new DefaultExecutorFactory()
+
+    def cleanup() {
+        factory.stop()
+    }
+
+    //TODO if you're looking here, please consider moving more tests from DefaultExecutorFactoryTest
+
+    def stopRethrowsFirstExecutionException() {
+        given:
+        def failure1 = new RuntimeException()
+        def runnable1 = { throw failure1 }
+
+        def failure2 = new RuntimeException()
+        def runnable2 = { Thread.sleep(100); throw failure2 }
+
+        when:
+        def executor = factory.create('')
+        executor.execute(runnable1)
+        executor.execute(runnable2)
+        executor.stop()
+
+        then:
+        def ex = thrown(RuntimeException)
+        ex.is(failure1)
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactoryTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactoryTest.groovy
new file mode 100644
index 0000000..4454eb3
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/concurrent/DefaultExecutorFactoryTest.groovy
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2010 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 org.gradle.internal.concurrent
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.MultithreadedTestCase
+import org.jmock.integration.junit4.JMock
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.TimeUnit
+
+ at RunWith(JMock)
+class DefaultExecutorFactoryTest extends MultithreadedTestCase {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final DefaultExecutorFactory factory = new DefaultExecutorFactory() {
+        def ExecutorService createExecutor(String displayName) {
+            return getExecutor()
+        }
+    }
+
+    @After
+    public void tearDown() {
+        factory.stop()
+    }
+
+    @Test
+    public void stopBlocksUntilAllJobsAreComplete() {
+        Runnable runnable = context.mock(Runnable.class)
+
+        context.checking {
+            one(runnable).run()
+            will {
+                syncAt(1)
+            }
+        }
+
+        def executor = factory.create('<display-name>')
+        executor.execute(runnable)
+
+        run {
+            expectBlocksUntil(1) {
+                executor.stop()
+            }
+        }
+    }
+    
+    @Test
+    public void factoryStopBlocksUntilAllJobsAreComplete() {
+        Runnable runnable = context.mock(Runnable.class)
+
+        context.checking {
+            one(runnable).run()
+            will {
+                syncAt(1)
+            }
+        }
+
+        def executor = factory.create('<display-name>')
+        executor.execute(runnable)
+
+        run {
+            expectBlocksUntil(1) {
+                factory.stop()
+            }
+        }
+    }
+
+    @Test
+    public void stopThrowsExceptionOnTimeout() {
+        Runnable runnable = context.mock(Runnable.class)
+
+        context.checking {
+            one(runnable).run()
+            will {
+                Thread.sleep(1000)
+            }
+        }
+
+        def executor = factory.create('<display-name>')
+        executor.execute(runnable)
+
+        expectTimesOut(500, TimeUnit.MILLISECONDS) {
+            try {
+                executor.stop(500, TimeUnit.MILLISECONDS)
+                org.junit.Assert.fail()
+            } catch (IllegalStateException e) {
+                org.junit.Assert.assertThat(e.message, org.hamcrest.Matchers.equalTo('Timeout waiting for concurrent jobs to complete.'))
+            }
+        }
+    }
+
+    @Test
+    public void cannotStopExecutorFromAnExecutorThread() {
+        Runnable runnable = context.mock(Runnable.class)
+
+        def executor = factory.create('<display-name>')
+
+        context.checking {
+            one(runnable).run()
+            will {
+                executor.stop()
+            }
+        }
+
+        executor.execute(runnable)
+
+        try {
+            executor.stop()
+            org.junit.Assert.fail()
+        } catch (IllegalStateException e) {
+            org.junit.Assert.assertThat(e.message, org.hamcrest.Matchers.equalTo('Cannot stop this executor from an executor thread.'))
+        }
+
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/id/CompositeIdGeneratorTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/id/CompositeIdGeneratorTest.groovy
new file mode 100644
index 0000000..ed974ff
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/id/CompositeIdGeneratorTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.id
+
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.gradle.util.Matchers.*
+import static org.junit.Assert.*
+
+import org.gradle.util.JUnit4GroovyMockery
+
+ at RunWith(JMock.class)
+class CompositeIdGeneratorTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final IdGenerator<?> target = context.mock(IdGenerator.class)
+    private final CompositeIdGenerator generator = new CompositeIdGenerator('scope', target)
+
+    @Test
+    public void createsACompositeId() {
+        Object original = 12
+        context.checking {
+            one(target).generateId()
+            will(returnValue(original))
+        }
+        Object id = generator.generateId()
+        assertThat(id, not(sameInstance(original)))
+        assertThat(id, not(equalTo(original)))
+        assertThat(id.toString(), equalTo('scope.12'))
+    }
+
+    @Test
+    public void compositeIdsAreNotEqualWhenOriginalIdsAreDifferent() {
+        context.checking {
+            one(target).generateId()
+            will(returnValue(12))
+            one(target).generateId()
+            will(returnValue('original'))
+        }
+
+        assertThat(generator.generateId(), not(equalTo(generator.generateId())))
+    }
+
+    @Test
+    public void compositeIdsAreNotEqualWhenScopesAreDifferent() {
+        context.checking {
+            exactly(2).of(target).generateId()
+            will(returnValue(12))
+        }
+
+        CompositeIdGenerator other = new CompositeIdGenerator('other', target)
+        assertThat(generator.generateId(), not(equalTo(other.generateId())))
+    }
+
+    @Test
+    public void compositeIdsAreEqualWhenOriginalIdsAreEqual() {
+        context.checking {
+            exactly(2).of(target).generateId()
+            will(returnValue(12))
+        }
+
+        assertThat(generator.generateId(), strictlyEqual(generator.generateId()))
+    }
+}
+
+
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/id/LongIdGeneratorTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/id/LongIdGeneratorTest.groovy
new file mode 100644
index 0000000..57f32b7
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/id/LongIdGeneratorTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.id
+
+import java.util.concurrent.CopyOnWriteArraySet
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+import org.gradle.util.MultithreadedTestCase
+
+class LongIdGeneratorTest extends MultithreadedTestCase {
+    private final LongIdGenerator generator = new LongIdGenerator()
+
+    @Test
+    public void generatesMonotonicallyIncreasingLongIdsStartingAtOne() {
+        assertThat(generator.generateId(), equalTo(1L))
+        assertThat(generator.generateId(), equalTo(2L))
+        assertThat(generator.generateId(), equalTo(3L))
+    }
+
+    @Test
+    public void generatesUniqueIdsWhenInvokedConcurrently() {
+        Set<Long> ids = new CopyOnWriteArraySet<Long>()
+
+        5.times {
+            start {
+                100.times {
+                    assertTrue(ids.add(generator.generateId()))
+                }
+            }
+        }
+        waitForAll()
+    }
+}
+
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy
index f7aa9cf..53612a6 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/AppleJvmTest.groovy
@@ -60,7 +60,7 @@ class AppleJvmTest extends Specification {
     }
 
     def "finds executable if java home supplied"() {
-        when:
+        given:
         def home = tmpDir.createDir("home")
         home.create {
             bin { file 'java' }
@@ -69,8 +69,8 @@ class AppleJvmTest extends Specification {
 
         AppleJvm jvm = new AppleJvm(os, home)
 
-        then:
-        home.file('bin/java').absolutePath == jvm.getExecutable('java').absolutePath
+        expect:
+        jvm.getExecutable('java').absolutePath == home.file('bin/java').absolutePath
     }
 
     def "provides decent feedback when executable not found"() {
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
index 241df64..8255b4f 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/jvm/JvmTest.groovy
@@ -37,9 +37,9 @@ class JvmTest extends Specification {
         System.properties['java.version'] = "1.$version" as String
 
         expect:
-        jvm."java$version"
-        !jvm."java$other1"
-        !jvm."java$other2"
+        jvm.javaVersion."java$version"
+        !jvm.javaVersion."java$other1"
+        !jvm.javaVersion."java$other2"
 
         where:
         version | other1 | other2
@@ -48,22 +48,6 @@ class JvmTest extends Specification {
         7       | 5      | 6
     }
 
-    def "uses system property to determine if compatible with Java 5"() {
-        System.properties['java.version'] = '1.5'
-
-        expect:
-        jvm.java5Compatible
-        !jvm.java6Compatible
-    }
-
-    def "uses system property to determine if compatible with Java 6"() {
-        System.properties['java.version'] = '1.6'
-
-        expect:
-        jvm.java5Compatible
-        jvm.java6Compatible
-    }
-
     def "looks for runtime Jar in Java home directory"() {
         TestFile javaHomeDir = tmpDir.createDir('jdk')
         TestFile runtimeJar = javaHomeDir.file('lib/rt.jar').createFile()
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/DirectInstantiatorTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/DirectInstantiatorTest.groovy
new file mode 100644
index 0000000..60475be
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/DirectInstantiatorTest.groovy
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.reflect
+
+import spock.lang.Specification
+
+import org.gradle.internal.UncheckedException
+
+class DirectInstantiatorTest extends Specification {
+    final DirectInstantiator instantiator = new DirectInstantiator()
+
+    def "creates instance with constructor parameters"() {
+        CharSequence param = Mock()
+
+        expect:
+        def result = instantiator.newInstance(SomeType, param)
+        result instanceof SomeType
+        result.result == param
+    }
+
+    def "creates instance with subtypes of constructor parameters"() {
+        expect:
+        def result = instantiator.newInstance(SomeType, "param")
+        result instanceof SomeType
+        result.result == "param"
+    }
+
+    def "creates instance with null constructor parameters"() {
+        expect:
+        def result = instantiator.newInstance(SomeType, [null] as Object[])
+        result instanceof SomeType
+        result.result == null
+    }
+
+    def "uses constructor which matches parameter types"() {
+        expect:
+        def stringResult = instantiator.newInstance(SomeTypeWithMultipleConstructors, "param")
+        stringResult instanceof SomeTypeWithMultipleConstructors
+        stringResult.result == "param"
+
+        and:
+        def numberResult = instantiator.newInstance(SomeTypeWithMultipleConstructors, 5)
+        numberResult instanceof SomeTypeWithMultipleConstructors
+        numberResult.result == 5
+    }
+
+    def "unboxes constructor parameters"() {
+        expect:
+        def result = instantiator.newInstance(SomeTypeWithPrimitiveTypes, true)
+        result instanceof SomeTypeWithPrimitiveTypes
+        result.result
+    }
+
+    def "fails when target class has ambiguous constructor"() {
+        when:
+        instantiator.newInstance(TypeWithAmbiguousConstructor, "param")
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Found multiple public constructors for ${TypeWithAmbiguousConstructor} which accept parameters [param]."
+
+        when:
+        instantiator.newInstance(TypeWithAmbiguousConstructor, true)
+
+        then:
+        e = thrown()
+        e.message == "Found multiple public constructors for ${TypeWithAmbiguousConstructor} which accept parameters [true]."
+    }
+
+    def "fails when target class has no matching public constructor"() {
+        def param = new Object()
+
+        when:
+        instantiator.newInstance(SomeType, param)
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [${param}]."
+
+        when:
+        instantiator.newInstance(SomeType, "a", "b")
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [a, b]."
+
+        when:
+        instantiator.newInstance(SomeType, false)
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [false]."
+
+        when:
+
+        instantiator.newInstance(SomeTypeWithPrimitiveTypes, "a")
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [a]."
+
+        when:
+
+        instantiator.newInstance(SomeTypeWithPrimitiveTypes, 22)
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [22]."
+
+        when:
+
+        instantiator.newInstance(SomeTypeWithPrimitiveTypes, [null] as Object[])
+
+        then:
+        e = thrown()
+        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [null]."
+    }
+
+    def "rethrows unchecked exception thrown by constructor"() {
+        when:
+        instantiator.newInstance(BrokenType, false)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == 'broken'
+    }
+
+    def "wraps checked exception thrown by constructor"() {
+        when:
+        instantiator.newInstance(BrokenType, true)
+
+        then:
+        UncheckedException e = thrown()
+        e.cause.message == 'broken'
+    }
+}
+
+class SomeType {
+    final Object result
+
+    SomeType(CharSequence result) {
+        this.result = result
+    }
+}
+
+class BrokenType {
+    BrokenType(Boolean checked) {
+        throw checked ? new Exception("broken") : new RuntimeException("broken")
+    }
+}
+
+class SomeTypeWithMultipleConstructors {
+    final Object result
+
+    SomeTypeWithMultipleConstructors(CharSequence result) {
+        this.result = result
+    }
+
+    SomeTypeWithMultipleConstructors(Number result) {
+        this.result = result
+    }
+}
+
+class SomeTypeWithPrimitiveTypes {
+    final Object result
+
+    SomeTypeWithPrimitiveTypes(boolean result) {
+        this.result = result
+    }
+}
+
+class TypeWithAmbiguousConstructor {
+    TypeWithAmbiguousConstructor(CharSequence param) {
+    }
+
+    TypeWithAmbiguousConstructor(String param) {
+    }
+
+    TypeWithAmbiguousConstructor(Boolean param) {
+    }
+
+    TypeWithAmbiguousConstructor(boolean param) {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaReflectionUtilTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaReflectionUtilTest.groovy
new file mode 100644
index 0000000..94995e5
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/reflect/JavaReflectionUtilTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.reflect;
+
+import spock.lang.Specification
+import org.gradle.internal.UncheckedException
+
+class JavaReflectionUtilTest extends Specification {
+    def myProperties = new MyProperties()
+
+    def "read property"() {
+        expect:
+        JavaReflectionUtil.readProperty(myProperties, "myProperty") == "myValue"
+    }
+
+    def "write property"() {
+        when:
+        JavaReflectionUtil.writeProperty(myProperties, "myProperty", "otherValue")
+
+        then:
+        JavaReflectionUtil.readProperty(myProperties, "myProperty") == "otherValue"
+    }
+
+    def "read boolean property"() {
+        expect:
+        JavaReflectionUtil.readProperty(myProperties, "myBooleanProperty") == true
+    }
+
+    def "write boolean property"() {
+        when:
+        JavaReflectionUtil.writeProperty(myProperties, "myBooleanProperty", false)
+
+        then:
+        JavaReflectionUtil.readProperty(myProperties, "myBooleanProperty") == false
+    }
+
+    def "read property that doesn't exist"() {
+        when:
+        JavaReflectionUtil.readProperty(myProperties, "unexisting")
+
+        then:
+        UncheckedException e = thrown()
+        e.cause instanceof NoSuchMethodException
+    }
+
+    def "write property that doesn't exist"() {
+        when:
+        JavaReflectionUtil.writeProperty(myProperties, "unexisting", "someValue")
+
+        then:
+        UncheckedException e = thrown()
+        e.cause instanceof NoSuchMethodException
+    }
+
+    static class MyProperties {
+        private String myProp = "myValue"
+        private boolean myBooleanProp = true
+
+        String getMyProperty() {  myProp }
+        void setMyProperty(String value) { myProp = value }
+
+        boolean isMyBooleanProperty() { myBooleanProp }
+        void setMyBooleanProperty(boolean value) { myBooleanProp = value }
+    }
+}
+
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.java b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.java
index 4f0d2b8..c1d03b9 100755
--- a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.java
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/DefaultServiceRegistryTest.java
@@ -117,7 +117,7 @@ public class DefaultServiceRegistryTest {
         try {
             registry.get(Object.class);
             fail();
-        } catch (IllegalArgumentException e) {
+        } catch (ServiceLookupException e) {
             assertThat(e.getMessage(), equalTo("Multiple services of type Object available in RegistryWithAmbiguousFactoryMethods."));
         }
     }
@@ -140,7 +140,7 @@ public class DefaultServiceRegistryTest {
         try {
             new RegistryWithDecoratorMethods();
             fail();
-        } catch (IllegalArgumentException e) {
+        } catch (ServiceLookupException e) {
             assertThat(e.getMessage(), equalTo("Cannot use decorator methods when no parent registry is provided."));
         }
     }
@@ -217,7 +217,7 @@ public class DefaultServiceRegistryTest {
         try {
             registry.getFactory(Object.class);
             fail();
-        } catch (IllegalArgumentException e) {
+        } catch (ServiceLookupException e) {
             assertThat(e.getMessage(), equalTo("Multiple factories for objects of type Object available in RegistryWithAmbiguousFactoryMethods."));
         }
     }
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ServiceLocatorTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ServiceLocatorTest.groovy
new file mode 100644
index 0000000..0b91e05
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/ServiceLocatorTest.groovy
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.service
+
+import spock.lang.Specification
+
+class ServiceLocatorTest extends Specification {
+    final ClassLoader classLoader = Mock()
+    final ServiceLocator serviceLocator = new ServiceLocator(classLoader)
+
+    def "locates service implementation class using resources of given ClassLoader"() {
+        def serviceFile = stream('org.gradle.ImplClass')
+
+        when:
+        def result = serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        result == String
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        1 * classLoader.loadClass('org.gradle.ImplClass') >> String
+    }
+
+    def "findServiceImplementationClass() returns null when no service meta data resource available"() {
+        when:
+        def result = serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        result == null
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> null
+    }
+
+    def "wraps implementation class load failure"() {
+        def serviceFile = stream('org.gradle.ImplClass')
+        def failure = new ClassNotFoundException()
+
+        when:
+        serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == "Could not load implementation class 'org.gradle.ImplClass' for service 'java.lang.String'."
+        e.cause == failure
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        1 * classLoader.loadClass('org.gradle.ImplClass') >> { throw failure }
+    }
+
+    def "ignores comments and whitespace in service meta data resource"() {
+        def serviceFile = stream('''#comment
+
+    org.gradle.ImplClass  
+''')
+
+        when:
+        def result = serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        result == String
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+        1 * classLoader.loadClass('org.gradle.ImplClass') >> String
+    }
+
+    def "findServiceImplementationClass() fails when no implementation class specified in service meta data resource"() {
+        def serviceFile = stream('#empty!')
+
+        when:
+        serviceLocator.findServiceImplementationClass(String.class)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == "Could not determine implementation class for service 'java.lang.String'."
+        e.cause.message == "No implementation class for service 'java.lang.String' specified in resource '${serviceFile}'."
+        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
+    }
+
+    def "findServiceImplementationClass() fails when implementation class specified in service meta data resource is not assignable to service type"() {
+        given:
+        implementationDeclared(String, Integer)
+
+        when:
+        serviceLocator.findServiceImplementationClass(String)
+
+        then:
+        RuntimeException e = thrown()
+        e.message == "Could not load implementation class 'java.lang.Integer' for service 'java.lang.String'."
+        e.cause.message == "Implementation class 'java.lang.Integer' is not assignable to service class 'java.lang.String'."
+    }
+
+    def "get() creates an instance of specified service implementation class"() {
+        given:
+        implementationDeclared(CharSequence, String)
+
+        when:
+        def result = serviceLocator.get(CharSequence)
+
+        then:
+        result instanceof String
+    }
+
+    def "get() caches service implementation instances"() {
+        given:
+        implementationDeclared(CharSequence, String)
+
+        when:
+        def obj1 = serviceLocator.get(CharSequence)
+        def obj2 = serviceLocator.get(CharSequence)
+
+        then:
+        obj1.is(obj2)
+    }
+
+    def "get() fails when no meta-data file found for service type"() {
+        when:
+        serviceLocator.get(CharSequence)
+
+        then:
+        UnknownServiceException e = thrown()
+        e.message == "Could not find meta-data resource 'META-INF/services/java.lang.CharSequence' for service 'java.lang.CharSequence'."
+    }
+
+    def "getFactory() returns a factory which creates instances of implementation class"() {
+        given:
+        implementationDeclared(CharSequence, String)
+
+        when:
+        def factory = serviceLocator.getFactory(CharSequence)
+        def obj1 = factory.create()
+        def obj2 = factory.create()
+
+        then:
+        obj1 instanceof String
+        obj2 instanceof String
+        !obj1.is(obj2)
+    }
+
+    def "getFactory() fails when no meta-data file found for service type"() {
+        when:
+        serviceLocator.getFactory(CharSequence)
+
+        then:
+        UnknownServiceException e = thrown()
+        e.message == "Could not find meta-data resource 'META-INF/services/java.lang.CharSequence' for service 'java.lang.CharSequence'."
+    }
+
+    def stream(String contents) {
+        URLStreamHandler handler = Mock()
+        URLConnection connection = Mock()
+        URL url = new URL("custom", "host", 12, "file", handler)
+        _ * handler.openConnection(url) >> connection
+        _ * connection.getInputStream() >> new ByteArrayInputStream(contents.bytes)
+        return url
+    }
+
+    def "newInstance() creates instances of implementation class"() {
+        given:
+        implementationDeclared(CharSequence, String)
+
+        when:
+        def result = serviceLocator.newInstance(CharSequence)
+
+        then:
+        result instanceof String
+    }
+    
+    def implementationDeclared(Class<?> serviceType, Class<?> implementationType) {
+        def serviceFile = stream(implementationType.name)
+        _ * classLoader.getResource("META-INF/services/${serviceType.name}") >> serviceFile
+        _ * classLoader.loadClass(implementationType.name) >> implementationType
+    }
+}
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/service/SynchronizedServiceRegistryTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/SynchronizedServiceRegistryTest.groovy
new file mode 100644
index 0000000..3623ccc
--- /dev/null
+++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/service/SynchronizedServiceRegistryTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2011 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 org.gradle.internal.service;
+
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 11/24/11
+ */
+public class SynchronizedServiceRegistryTest extends Specification {
+
+    def delegate = Mock(ServiceRegistry)
+    def reg = new SynchronizedServiceRegistry(delegate);
+
+    def "gets services from delegate"() {
+        when:
+        reg.get(Object)
+        then:
+        1 * delegate.get(Object)
+        when:
+        reg.getFactory(String)
+        then:
+        1 * delegate.getFactory(String)
+        when:
+        reg.newInstance(Integer)
+        then:
+        1 * delegate.newInstance(Integer)
+    }
+}
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
index beff97d..3d46951 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CheckstylePluginIntegrationTest.groovy
@@ -44,15 +44,27 @@ class CheckstylePluginIntegrationTest extends WellBehavedPluginTest {
     }
 
     def "analyze bad code"() {
-        file("src/main/java/org/gradle/class1.java") << "package org.gradle; class class1 { }"
-        file("src/test/java/org/gradle/testclass1.java") << "package org.gradle; class testclass1 { }"
-        file("src/main/groovy/org/gradle/class2.java") << "package org.gradle; class class2 { }"
-        file("src/test/groovy/org/gradle/testclass2.java") << "package org.gradle; class testclass2 { }"
+        badCode()
 
         expect:
         fails("check")
         failure.assertHasDescription("Execution failed for task ':checkstyleMain'")
-        failure.assertThatCause(startsWith("Checkstyle rule violations were found. See the report at"))
+        failure.assertThatCause(startsWith("Checkstyle rule violations were found. See the report at:"))
+        file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class1"))
+        file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class2"))
+    }
+
+    def "can ignore failures"() {
+        badCode()
+        buildFile << """
+            checkstyle {
+                ignoreFailures = true
+            }
+        """
+
+        expect:
+        succeeds("check")
+        output.contains("Checkstyle rule violations were found. See the report at:")
         file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class1"))
         file("build/reports/checkstyle/main.xml").assertContents(containsClass("org.gradle.class2"))
     }
@@ -93,6 +105,13 @@ class CheckstylePluginIntegrationTest extends WellBehavedPluginTest {
         file('src/test/groovy/org/gradle/TestClass2.java') << 'package org.gradle; class TestClass1 { }'
     }
 
+    private badCode() {
+        file("src/main/java/org/gradle/class1.java") << "package org.gradle; class class1 { }"
+        file("src/test/java/org/gradle/testclass1.java") << "package org.gradle; class testclass1 { }"
+        file("src/main/groovy/org/gradle/class2.java") << "package org.gradle; class class2 { }"
+        file("src/test/groovy/org/gradle/testclass2.java") << "package org.gradle; class testclass2 { }"
+    }
+
     private Matcher<String> containsClass(String className) {
         containsLine(containsString(className.replace(".", File.separator)))
     }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy
index e1de805..162f9d9 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeNarcPluginIntegrationTest.groovy
@@ -74,26 +74,46 @@ class CodeNarcPluginIntegrationTest extends WellBehavedPluginTest {
     }
     
     def "analyze bad code"() {
-        file("src/main/groovy/org/gradle/class1.java") << "package org.gradle; class class1 { }"
-        file("src/main/groovy/org/gradle/Class2.groovy") << "package org.gradle; class Class2 { }"
-        file("src/test/groovy/org/gradle/TestClass1.java") << "package org.gradle; class TestClass1 { }"
-        file("src/test/groovy/org/gradle/testclass2.groovy") << "package org.gradle; class testclass2 { }"
+        badCode()
 
         expect:
         fails("check")
         failure.assertHasDescription("Execution failed for task ':codenarcTest'")
-        failure.assertThatCause(startsWith("CodeNarc rule violations were found. See the report at "))
+        failure.assertThatCause(startsWith("CodeNarc rule violations were found. See the report at:"))
         !file("build/reports/codenarc/main.html").text.contains("Class2")
         file("build/reports/codenarc/test.html").text.contains("testclass2")
     }
 
+    def "can ignore failures"() {
+        badCode()
+        buildFile << """
+            codenarc {
+                ignoreFailures = true
+            }
+        """
+
+        expect:
+        succeeds("check")
+        output.contains("CodeNarc rule violations were found. See the report at:")
+        !file("build/reports/codenarc/main.html").text.contains("Class2")
+        file("build/reports/codenarc/test.html").text.contains("testclass2")
+
+    }
+
     private goodCode() {
         file("src/main/groovy/org/gradle/class1.java") << "package org.gradle; class class1 { }"
         file("src/test/groovy/org/gradle/testclass1.java") << "package org.gradle; class testclass1 { }"
         file("src/main/groovy/org/gradle/Class2.groovy") << "package org.gradle; class Class2 { }"
         file("src/test/groovy/org/gradle/TestClass2.groovy") << "package org.gradle; class TestClass2 { }"
     }
-    
+
+    private badCode() {
+        file("src/main/groovy/org/gradle/class1.java") << "package org.gradle; class class1 { }"
+        file("src/main/groovy/org/gradle/Class2.groovy") << "package org.gradle; class Class2 { }"
+        file("src/test/groovy/org/gradle/TestClass1.java") << "package org.gradle; class TestClass1 { }"
+        file("src/test/groovy/org/gradle/testclass2.groovy") << "package org.gradle; class testclass2 { }"
+    }
+
     private void writeBuildFile() {
         file("build.gradle") << """
 apply plugin: "groovy"
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
index 68ca63f..dcda76a 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/CodeQualityPluginIntegrationTest.groovy
@@ -175,7 +175,7 @@ dependencies { groovy localGroovy() }
 
         ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
         failure.assertHasDescription('Execution failed for task \':codenarcMain\'')
-        failure.assertThatCause(startsWith('CodeNarc rule violations were found. See the report at '))
+        failure.assertThatCause(startsWith('CodeNarc rule violations were found. See the report at:'))
 
         testFile('build/reports/codenarc/main.html').assertExists()
     }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/FindBugsPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/FindBugsPluginIntegrationTest.groovy
index b7d5395..83dc8bf 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/FindBugsPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/FindBugsPluginIntegrationTest.groovy
@@ -35,18 +35,32 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         goodCode()
         expect:
         succeeds("check")
-		file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
-		file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
+        file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
+        file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
     }
 
     void "analyze bad code"() {
-        file("src/main/java/org/gradle/Class1.java") << "package org.gradle; class Class1 { public boolean equals(Object arg) { return true; } }"
+        badCode()
 
         expect:
         fails("check")
-		failure.assertHasDescription("Execution failed for task ':findbugsMain'")
-        failure.assertThatCause(startsWith("FindBugs rule violations were found. See the report at"))
-		file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
+        failure.assertHasDescription("Execution failed for task ':findbugsMain'")
+        failure.assertThatCause(startsWith("FindBugs rule violations were found. See the report at:"))
+        file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
+    }
+
+    void "can ignore failures"() {
+        badCode()
+        buildFile << """
+            findbugs {
+                ignoreFailures = true
+            }
+        """
+
+        expect:
+        succeeds("check")
+        output.contains("FindBugs rule violations were found. See the report at:")
+        file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
     }
 
     def "is incremental"() {
@@ -81,7 +95,7 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
 
         failure.assertHasCause "Findbugs tasks can only have one report enabled"
     }
-    
+
     def "can generate html reports"() {
         given:
         buildFile << """
@@ -90,13 +104,13 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
                 html.enabled true
             }
         """
-        
+
         and:
         goodCode()
-        
+
         when:
         run "findbugsMain"
-        
+
         then:
         file("build/reports/findbugs/main.html").exists()
     }
@@ -121,15 +135,31 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         !file("build/reports/findbugs/main.xml").exists()
     }
 
-    private goodCode() {
-        file("src/main/java/org/gradle/Class1.java") << "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
-        file("src/test/java/org/gradle/Class1Test.java") << "package org.gradle; class Class1Test { public boolean isFoo(Object arg) { return true; } }"
+    def "can analyze a lot of classes"() {
+        goodCode(800)
+        expect:
+        succeeds("check")
+        file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
+        file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class800"))
+        file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
+        file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class800Test"))
+    }
+
+    private goodCode(int numberOfClasses = 1) {
+        1.upto(numberOfClasses) {
+            file("src/main/java/org/gradle/Class${it}.java") << "package org.gradle; class Class${it} { public boolean isFoo(Object arg) { return true; } }"
+            file("src/test/java/org/gradle/Class${it}Test.java") << "package org.gradle; class Class${it}Test { public boolean isFoo(Object arg) { return true; } }"
+        }
+    }
+
+    private badCode() {
+        file("src/main/java/org/gradle/Class1.java") << "package org.gradle; class Class1 { public boolean equals(Object arg) { return true; } }"
     }
 
     private Matcher<String> containsClass(String className) {
         containsLine(containsString(className.replace(".", File.separator)))
     }
-  
+
     private void writeBuildFile() {
         file("build.gradle") << """
         apply plugin: "java"
@@ -138,6 +168,7 @@ class FindBugsPluginIntegrationTest extends WellBehavedPluginTest {
         repositories {
             mavenCentral()
         }
+
         """
     }
 }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
index 7d2bee4..dde6fb3 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
@@ -41,25 +41,30 @@ class PmdPluginIntegrationTest extends WellBehavedPluginTest {
         file("build/reports/pmd/test.xml").exists()
     }
 
-    private goodCode() {
-        file("src/main/java/org/gradle/Class1.java") <<
-                "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
-        file("src/test/java/org/gradle/Class1Test.java") <<
-                "package org.gradle; class Class1Test { public boolean isFoo(Object arg) { return true; } }"
-    }
-
     def "analyze bad code"() {
-        file("src/main/java/org/gradle/Class1.java") <<
-                "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
-        file("src/test/java/org/gradle/Class1Test.java") <<
-                "package org.gradle; class Class1Test { {} public boolean equals(Object arg) { return true; } }"
-        
+        badCode()
+
         expect:
         fails("check")
-		failure.assertHasDescription("Execution failed for task ':pmdTest'")
-		failure.assertThatCause(containsString("PMD found 2 rule violations"))
+        failure.assertHasDescription("Execution failed for task ':pmdTest'")
+        failure.assertThatCause(containsString("2 PMD rule violations were found. See the report at:"))
+        file("build/reports/pmd/main.xml").assertContents(not(containsClass("org.gradle.Class1")))
+        file("build/reports/pmd/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
+    }
+
+    void "can ignore failures"() {
+        badCode()
+        buildFile << """
+            pmd {
+                ignoreFailures = true
+            }
+        """
+
+        expect:
+        succeeds("check")
         file("build/reports/pmd/main.xml").assertContents(not(containsClass("org.gradle.Class1")))
-		file("build/reports/pmd/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
+        file("build/reports/pmd/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
+        output.contains("2 PMD rule violations were found. See the report at:")
     }
 
     def "can configure reporting"() {
@@ -77,11 +82,11 @@ class PmdPluginIntegrationTest extends WellBehavedPluginTest {
         """
         expect:
         succeeds("check")
-        
+
         !file("build/reports/pmd/main.xml").exists()
         file("htmlReport.html").exists()
     }
-    
+
     private void writeBuildFile() {
         file("build.gradle") << """
 apply plugin: "java"
@@ -96,4 +101,18 @@ repositories {
     private Matcher<String> containsClass(String className) {
         containsLine(containsString(className.replace(".", File.separator)))
     }
+
+    private goodCode() {
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") <<
+                "package org.gradle; class Class1Test { public boolean isFoo(Object arg) { return true; } }"
+    }
+
+    private badCode() {
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") <<
+                "package org.gradle; class Class1Test { {} public boolean equals(Object arg) { return true; } }"
+    }
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy
index 71ecb3a..b4468b0 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.groovy
@@ -17,12 +17,13 @@ package org.gradle.api.plugins.quality
 
 import org.gradle.api.GradleException
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.Instantiator
 import org.gradle.api.internal.project.IsolatedAntBuilder
 import org.gradle.api.plugins.quality.internal.CheckstyleReportsImpl
 import org.gradle.api.reporting.Reporting
-import org.gradle.util.DeprecationLogger
 import org.gradle.api.tasks.*
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.logging.ConsoleRenderer
+import org.gradle.util.DeprecationLogger
 
 /**
  * Runs Checkstyle against some source files.
@@ -157,11 +158,17 @@ class Checkstyle extends SourceTask implements VerificationTask, Reporting<Check
                 }
             }
 
-            if (!getIgnoreFailures() && ant.project.properties[propertyName]) {
-                if (reports.xml.enabled) {
-                    throw new GradleException("Checkstyle rule violations were found. See the report at ${reports.xml.destination}.")
+            if (ant.project.properties[propertyName]) {
+                def message = "Checkstyle rule violations were found."
+                def report = reports.firstEnabled
+                if (report) {
+                    def reportUrl = new ConsoleRenderer().asClickableFileUrl(report.destination)
+                    message += " See the report at: $reportUrl"
+                }
+                if (getIgnoreFailures()) {
+                    logger.warn(message)
                 } else {
-                    throw new GradleException("Checkstyle rule violations were found")
+                    throw new GradleException(message)
                 }
             }
         }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy
index 76ab134..edf1a53 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.groovy
@@ -15,19 +15,17 @@
  */
 package org.gradle.api.plugins.quality
 
-//import org.gradle.api.plugins.quality.internal.ConsoleReportWriter
-
-
 import org.gradle.api.GradleException
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.Instantiator
 import org.gradle.api.internal.project.IsolatedAntBuilder
 import org.gradle.api.logging.LogLevel
 import org.gradle.api.plugins.quality.internal.CodeNarcReportsImpl
 import org.gradle.api.reporting.Report
 import org.gradle.api.reporting.Reporting
-import org.gradle.util.DeprecationLogger
 import org.gradle.api.tasks.*
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.logging.ConsoleRenderer
+import org.gradle.util.DeprecationLogger
 
 /**
  * Runs CodeNarc against some source files.
@@ -113,14 +111,17 @@ class CodeNarc extends SourceTask implements VerificationTask, Reporting<CodeNar
                 }
             } catch (Exception e) {
                 if (e.message.matches('Exceeded maximum number of priority \\d* violations.*')) {
+                    def message = "CodeNarc rule violations were found."
+                    def report = reports.firstEnabled
+                    if (report) {
+                        def reportUrl = new ConsoleRenderer().asClickableFileUrl(report.destination)
+                        message += " See the report at: $reportUrl"
+                    }
                     if (getIgnoreFailures()) {
+                        logger.warn(message)
                         return
                     }
-                    if (reports.html.enabled) {
-                        throw new GradleException("CodeNarc rule violations were found. See the report at ${reports.html.destination}.", e)
-                    } else {
-                        throw new GradleException("CodeNarc rule violations were found.", e)
-                    }
+                    throw new GradleException(message, e)
                 }
                 throw e
             }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy
index 94a1606..7de554e 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy
@@ -17,15 +17,16 @@ package org.gradle.api.plugins.quality
 
 import org.gradle.api.GradleException
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.Instantiator
 import org.gradle.api.plugins.quality.internal.FindBugsReportsImpl
-import org.gradle.api.plugins.quality.internal.findbugs.FindBugsDaemonManager
+import org.gradle.api.plugins.quality.internal.findbugs.FindBugsWorkerManager
 import org.gradle.api.plugins.quality.internal.findbugs.FindBugsResult
 import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpec
 import org.gradle.api.plugins.quality.internal.findbugs.FindBugsSpecBuilder
 import org.gradle.api.reporting.Reporting
-import org.gradle.api.reporting.SingleFileReport
 import org.gradle.api.tasks.*
+import org.gradle.api.logging.LogLevel
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.logging.ConsoleRenderer
 
 /**
  * Analyzes code with <a href="http://findbugs.sourceforge.net">FindBugs</a>.
@@ -107,21 +108,33 @@ class FindBugs extends SourceTask implements VerificationTask, Reporting<FindBug
                 .configureReports(reports)
 
         FindBugsSpec spec = argumentBuilder.build()
-        FindBugsDaemonManager manager = new FindBugsDaemonManager();
-        FindBugsResult findbugsResult = manager.runDaemon(getProject(), getFindbugsClasspath(), spec)
+        FindBugsWorkerManager manager = new FindBugsWorkerManager();
+
+        logging.captureStandardOutput(LogLevel.DEBUG)
+        logging.captureStandardError(LogLevel.DEBUG)
+
+        FindBugsResult findbugsResult = manager.runWorker(getProject(), getFindbugsClasspath(), spec)
         evaluateResult(findbugsResult);
     }
 
     void evaluateResult(FindBugsResult findbugsResult) {
+        if (findbugsResult.exception){
+            throw new GradleException("FindBugs encountered an error. Run with --debug to get more information.", findbugsResult.exception)
+        }
         if (findbugsResult.errorCount){
             throw new GradleException("FindBugs encountered an error. Run with --debug to get more information.")
         }
-        if (findbugsResult.bugCount && !ignoreFailures) {
-            SingleFileReport reportSetup = reports.firstEnabled
-            if (reports.firstEnabled) {
-                throw new GradleException("FindBugs rule violations were found. See the report at ${reportSetup.destination}.")
+        if (findbugsResult.bugCount) {
+            def message = "FindBugs rule violations were found."
+            def report = reports.firstEnabled
+            if (report) {
+                def reportUrl = new ConsoleRenderer().asClickableFileUrl(report.destination)
+                message += " See the report at: $reportUrl"
+            }
+            if (getIgnoreFailures()) {
+                logger.warn(message)
             } else {
-                throw new GradleException("FindBugs rule violations were found.")
+                throw new GradleException(message)
             }
         }
     }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDepend.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDepend.groovy
index 3b8d167..0dac435 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDepend.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/JDepend.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.plugins.quality
 import org.gradle.api.DefaultTask
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.project.IsolatedAntBuilder
 import org.gradle.api.plugins.quality.internal.JDependReportsImpl
 import org.gradle.api.reporting.Reporting
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
index 88682cc..54cf166 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
@@ -16,11 +16,13 @@
 package org.gradle.api.plugins.quality
 
 import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.project.IsolatedAntBuilder
 import org.gradle.api.plugins.quality.internal.PmdReportsImpl
 import org.gradle.api.reporting.Reporting
 import org.gradle.api.tasks.*
+import org.gradle.logging.ConsoleRenderer
+import org.gradle.api.GradleException
 
 /**
  * Runs a set of static code analysis rules on Java source code files and
@@ -67,7 +69,7 @@ class Pmd extends SourceTask implements VerificationTask, Reporting<PmdReports>
         def antBuilder = services.get(IsolatedAntBuilder)
         antBuilder.withClasspath(getPmdClasspath()).execute {
             ant.taskdef(name: 'pmd', classname: 'net.sourceforge.pmd.ant.PMDTask')
-            ant.pmd(failOnRuleViolation: !getIgnoreFailures()) {
+            ant.pmd(failOnRuleViolation: false, failuresPropertyName: "pmdFailureCount") {
                 getSource().addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
                 getRuleSets().each {
                     ruleset(it)
@@ -84,6 +86,21 @@ class Pmd extends SourceTask implements VerificationTask, Reporting<PmdReports>
                     formatter(type: 'xml', toFile: reports.xml.destination)
                 }
             }
+
+            def failureCount = ant.project.properties["pmdFailureCount"]
+            if (failureCount) {
+                def message = "$failureCount PMD rule violations were found."
+                def report = reports.firstEnabled
+                if (report) {
+                    def reportUrl = new ConsoleRenderer().asClickableFileUrl(report.destination)
+                    message += " See the report at: $reportUrl"
+                }
+                if (getIgnoreFailures()) {
+                    logger.warn(message)
+                } else {
+                    throw new GradleException(message)
+                }
+            }
         }
     }
 
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemon.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemon.java
deleted file mode 100644
index cef9116..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemon.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs;
-
-import org.gradle.internal.Stoppable;
-
-public interface FindBugsDaemon extends Stoppable {
-    FindBugsResult execute();
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClient.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClient.java
deleted file mode 100644
index 70e6db5..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClient.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs;
-
-import org.gradle.internal.UncheckedException;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.SynchronousQueue;
-
-public class FindBugsDaemonClient implements FindBugsDaemonClientProtocol {
-
-    private final BlockingQueue<FindBugsResult> findbugsResults = new SynchronousQueue<FindBugsResult>();
-
-    public void executed(FindBugsResult result) {
-        try {
-            findbugsResults.put(result);
-        } catch (InterruptedException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    public FindBugsResult getResult() {
-        try {
-            return findbugsResults.take();
-        } catch (InterruptedException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClientProtocol.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClientProtocol.java
deleted file mode 100644
index 677d6de..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonClientProtocol.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs;
-
-public interface FindBugsDaemonClientProtocol {
-    void executed(FindBugsResult result);
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonManager.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonManager.groovy
deleted file mode 100644
index 9d3dbf3..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonManager.groovy
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs
-
-import org.gradle.api.file.FileCollection
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.logging.Logger
-import org.gradle.api.logging.Logging
-import org.gradle.process.internal.JavaExecHandleBuilder
-import org.gradle.process.internal.WorkerProcess
-import org.gradle.process.internal.WorkerProcessBuilder
-
-class FindBugsDaemonManager {
-    private final Logger logger = Logging.getLogger(getClass())
-
-    public FindBugsResult runDaemon(ProjectInternal project, FileCollection findBugsClasspath, FindBugsSpec spec) {
-        logger.info("Starting Gradle findbugs daemon.");
-        if (logger.isDebugEnabled()) {
-            logger.debug(findBugsClasspath.asPath);
-        }
-
-        WorkerProcess process = createWorkerProcess(project, findBugsClasspath, spec);
-        process.start();
-
-        FindBugsDaemonClient clientCallBack = new FindBugsDaemonClient()
-        process.connection.addIncoming(FindBugsDaemonClientProtocol.class, clientCallBack);
-        FindBugsResult result = clientCallBack.getResult();
-
-        process.waitForStop();
-        logger.info("Gradle findbugs daemon stopped.");
-        return result;
-    }
-
-    private WorkerProcess createWorkerProcess(ProjectInternal project, FileCollection findBugsClasspath, FindBugsSpec spec) {
-        WorkerProcessBuilder builder = project.getServices().getFactory(WorkerProcessBuilder.class).create();
-        builder.setLogLevel(project.getGradle().getStartParameter().getLogLevel());
-        builder.applicationClasspath(findBugsClasspath);   //findbugs classpath
-        builder.sharedPackages(Arrays.asList("edu.umd.cs.findbugs"));
-        JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
-        javaCommand.setWorkingDir(project.getRootProject().getProjectDir());
-
-        WorkerProcess process = builder.worker(new FindBugsDaemonServer(spec)).build()
-        return process
-    }
-}
\ No newline at end of file
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonServer.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonServer.java
deleted file mode 100644
index 6671246..0000000
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsDaemonServer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs;
-
-import org.gradle.api.Action;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.process.internal.WorkerProcessContext;
-
-import java.io.Serializable;
-
-public class FindBugsDaemonServer implements Action<WorkerProcessContext>, Serializable {
-    private static final Logger LOGGER = Logging.getLogger(FindBugsDaemonServer.class);
-    private FindBugsSpec spec;
-
-    public FindBugsDaemonServer(FindBugsSpec spec) {
-        this.spec = spec;
-    }
-
-    public void execute(WorkerProcessContext context) {
-        final FindBugsResult result = execute();
-        final FindBugsDaemonClientProtocol clientProtocol = context.getServerConnection().addOutgoing(FindBugsDaemonClientProtocol.class);
-        clientProtocol.executed(result);
-    }
-
-    public FindBugsResult execute() {
-        LOGGER.info("Executing findbugs daemon.");
-        try {
-            FindBugsExecuter findBugsExecuter = new FindBugsExecuter(this);
-            return findBugsExecuter.runFindbugs(spec);
-        } catch (Exception e) {
-            LOGGER.warn("Exception occured while running FindBugs.", e);
-            return new FindBugsResult(0, 0, 1); //mark result with error count 1
-        }
-    }
-}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuter.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuter.java
index fe45fc8..4ed3c51 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuter.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsExecuter.java
@@ -21,46 +21,30 @@ import edu.umd.cs.findbugs.FindBugs2;
 import edu.umd.cs.findbugs.IFindBugsEngine;
 import edu.umd.cs.findbugs.TextUICommandLine;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.io.PrintStream;
 import java.io.Serializable;
 import java.util.List;
 
 public class FindBugsExecuter implements Serializable {
-    private final FindBugsDaemonServer findBugsDaemonServer;
 
-    public FindBugsExecuter(FindBugsDaemonServer findBugsDaemonServer) {
-        this.findBugsDaemonServer = findBugsDaemonServer;
+    public FindBugsExecuter() {
     }
 
     FindBugsResult runFindbugs(FindBugsSpec spec) throws IOException, InterruptedException {
         final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
-        final PrintStream origOut = System.out;
-        final PrintStream origErr = System.err;
         try {
-//            FindBugsDaemonServer.LOGGER.debug("Running findbugs specification {}", spec);
             final List<String> args = spec.getArguments();
             String[] strArray = new String[args.size()];
             args.toArray(strArray);
-            // TODO RG: replace ByteArrayOutputStream by OutputStream that handles logging directly.
-            // TODO RG: use seperate streams for out and err.
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            System.setOut(new PrintStream(baos));
-            System.setErr(new PrintStream(baos));
-            Thread.currentThread().setContextClassLoader(FindBugs2.class.getClassLoader());
 
+            Thread.currentThread().setContextClassLoader(FindBugs2.class.getClassLoader());
             FindBugs2 findBugs2 = new FindBugs2();
             TextUICommandLine commandLine = new TextUICommandLine();
             FindBugs.processCommandLine(commandLine, strArray, findBugs2);
             findBugs2.execute();
 
-//            FindBugsDaemonServer.LOGGER.debug(baos.toString());
-//            FindBugsDaemonServer.LOGGER.info("Successfully executed in findbugs daemon.");
             return createFindbugsResult(findBugs2);
         } finally {
-            System.setOut(origOut);
-            System.setErr(origErr);
             Thread.currentThread().setContextClassLoader(contextClassLoader);
         }
     }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsResult.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsResult.java
index 4ec2bb5..5032b15 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsResult.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsResult.java
@@ -23,11 +23,17 @@ public class FindBugsResult implements Serializable {
     private final int bugCount;
     private final int missingClassCount;
     private final int errorCount;
+    private final Exception exception;
 
     public FindBugsResult(int bugCount, int missingClassCount, int errorCount) {
+        this(bugCount, missingClassCount, errorCount, null);
+    }
+
+    public FindBugsResult(int bugCount, int missingClassCount, int errorCount, Exception exception) {
         this.bugCount = bugCount;
         this.missingClassCount = missingClassCount;
         this.errorCount = errorCount;
+        this.exception = exception;
     }
 
     public int getBugCount() {
@@ -41,4 +47,8 @@ public class FindBugsResult implements Serializable {
     public int getErrorCount() {
         return errorCount;
     }
+
+    public Exception getException() {
+        return exception;
+    }
 }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerClient.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerClient.java
new file mode 100644
index 0000000..01eea41
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerClient.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs;
+
+import org.gradle.internal.UncheckedException;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+
+public class FindBugsWorkerClient implements FindBugsWorkerClientProtocol {
+
+    private final BlockingQueue<FindBugsResult> findbugsResults = new SynchronousQueue<FindBugsResult>();
+
+    public void executed(FindBugsResult result) {
+        try {
+            findbugsResults.put(result);
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public FindBugsResult getResult() {
+        try {
+            return findbugsResults.take();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerClientProtocol.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerClientProtocol.java
new file mode 100644
index 0000000..f1fa76b
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerClientProtocol.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs;
+
+public interface FindBugsWorkerClientProtocol {
+    void executed(FindBugsResult result);
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy
new file mode 100644
index 0000000..09825ff
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.process.internal.JavaExecHandleBuilder
+import org.gradle.process.internal.WorkerProcess
+import org.gradle.process.internal.WorkerProcessBuilder
+
+class FindBugsWorkerManager {
+    public FindBugsResult runWorker(ProjectInternal project, FileCollection findBugsClasspath, FindBugsSpec spec) {
+        WorkerProcess process = createWorkerProcess(project, findBugsClasspath, spec);
+        process.start();
+
+        FindBugsWorkerClient clientCallBack = new FindBugsWorkerClient()
+        process.connection.addIncoming(FindBugsWorkerClientProtocol.class, clientCallBack);
+        FindBugsResult result = clientCallBack.getResult();
+
+        process.waitForStop();
+        return result;
+    }
+
+    private WorkerProcess createWorkerProcess(ProjectInternal project, FileCollection findBugsClasspath, FindBugsSpec spec) {
+        WorkerProcessBuilder builder = project.getServices().getFactory(WorkerProcessBuilder.class).create();
+        builder.setLogLevel(project.getGradle().getStartParameter().getLogLevel());
+        builder.applicationClasspath(findBugsClasspath);   //findbugs classpath
+        builder.sharedPackages(Arrays.asList("edu.umd.cs.findbugs"));
+        JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
+        javaCommand.setWorkingDir(project.getRootProject().getProjectDir());
+
+        WorkerProcess process = builder.worker(new FindBugsWorkerServer(spec)).build()
+        return process
+    }
+}
\ No newline at end of file
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerServer.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerServer.java
new file mode 100644
index 0000000..12e899d
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerServer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 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 org.gradle.api.plugins.quality.internal.findbugs;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.process.internal.WorkerProcessContext;
+
+import java.io.Serializable;
+
+public class FindBugsWorkerServer implements Action<WorkerProcessContext>, Serializable {
+    private static final Logger LOGGER = Logging.getLogger(FindBugsWorkerServer.class);
+    private FindBugsSpec spec;
+
+    public FindBugsWorkerServer(FindBugsSpec spec) {
+        this.spec = spec;
+    }
+
+    public void execute(WorkerProcessContext context) {
+        final FindBugsResult result = execute();
+        final FindBugsWorkerClientProtocol clientProtocol = context.getServerConnection().addOutgoing(FindBugsWorkerClientProtocol.class);
+        clientProtocol.executed(result);
+    }
+
+    public FindBugsResult execute() {
+        LOGGER.debug("Executing findbugs worker.");
+        try {
+            FindBugsExecuter findBugsExecuter = new FindBugsExecuter();
+            return findBugsExecuter.runFindbugs(spec);
+        } catch (Exception e) {
+            LOGGER.warn("Exception occurred while running FindBugs.", e);
+            return new FindBugsResult(0, 0, 1, e); //mark result with error count 1
+        }
+    }
+}
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsTest.groovy
index 95fcd42..f2102fe 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/FindBugsTest.groovy
@@ -98,9 +98,9 @@ class FindBugsTest extends Specification {
     def "missingClassCount > 0 does not cause failing FindBugsTask"() {
         setup:
         FindBugsResult result = Mock(FindBugsResult)
-        when:
         result.missingClassCount >> 1
-        then:
+
+        expect:
         findbugs.evaluateResult(result);
     }
 }
diff --git a/subprojects/core-impl/core-impl.gradle b/subprojects/core-impl/core-impl.gradle
index 064d6ed..0a551e7 100644
--- a/subprojects/core-impl/core-impl.gradle
+++ b/subprojects/core-impl/core-impl.gradle
@@ -1,5 +1,11 @@
 apply plugin: "groovy"
 
+import org.gradle.build.JarJar
+
+configurations {
+    mvn3Input
+}
+
 dependencies {
     groovy libraries.groovy
 
@@ -11,8 +17,31 @@ dependencies {
     compile libraries.ivy
     compile libraries.slf4j_api
     compile libraries.maven_ant_tasks
+    compile libraries.nekohtml
 
     testCompile libraries.junit
+
+    compile files(["$buildDir/libs/jarjar/jarjar-maven-settings-3.0.4.jar",
+            "$buildDir/libs/jarjar/jarjar-maven-settings-builder-3.0.4.jar",
+            "$buildDir/libs/jarjar/jarjar-plexus-cipher-1.4.jar",
+            "$buildDir/libs/jarjar/jarjar-plexus-component-annotations-1.5.5.jar",
+            "$buildDir/libs/jarjar/jarjar-plexus-interpolation-1.14.jar",
+            "$buildDir/libs/jarjar/jarjar-plexus-sec-dispatcher-1.3.jar",
+            "$buildDir/libs/jarjar/jarjar-plexus-utils-2.0.6.jar"]) {
+        builtBy tasks.withType(JarJar)
+    }
+
+    mvn3Input libraries.maven3_settings_builder
+}
+configurations.mvn3Input.files.each{libFile->
+   task "jarjar-${libFile.name}"(type: JarJar) {
+        inputFile = libFile
+        outputFile = file("$buildDir/libs/jarjar/jarjar-${libFile.name}")
+        rule('org.**', 'jarjar.org. at 1')
+    }
 }
 
-useTestFixtures()
+def allJarJars = tasks.withType(JarJar)
+ideaModule.dependsOn allJarJars // I expected that buildable file collections links the ideaModule to the allJarJars already.
+
+useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
index 0c1fc29..2758889 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
@@ -22,7 +22,7 @@ import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
 import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
@@ -250,6 +250,7 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         private ConfigurationContainerInternal configurationContainer;
         private DependencyHandler dependencyHandler;
         private DefaultArtifactHandler artifactHandler;
+        private ResolverFactory resolverFactory;
 
         private DefaultDependencyResolutionServices(ServiceRegistry parent, FileResolver fileResolver, DependencyMetaDataProvider dependencyMetaDataProvider, ProjectFinder projectFinder, DomainObjectContext domainObjectContext) {
             this.parent = parent;
@@ -266,17 +267,26 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
             return repositoryHandler;
         }
 
+        public ResolverFactory getResolverFactory() {
+            if (resolverFactory == null) {
+                Instantiator instantiator = parent.get(Instantiator.class);
+                //noinspection unchecked
+                resolverFactory = new DefaultResolverFactory(
+                        get(LocalMavenRepositoryLocator.class),
+                        fileResolver,
+                        instantiator,
+                        get(RepositoryTransportFactory.class),
+                        get(LocallyAvailableResourceFinder.class),
+                        get(ByUrlCachedExternalResourceIndex.class)
+                );
+            }
+
+            return resolverFactory;
+        }
+
         private DefaultRepositoryHandler createRepositoryHandler() {
             Instantiator instantiator = parent.get(Instantiator.class);
-            @SuppressWarnings("unchecked") ResolverFactory resolverFactory = new DefaultResolverFactory(
-                    get(LocalMavenRepositoryLocator.class),
-                    fileResolver,
-                    instantiator,
-                    get(RepositoryTransportFactory.class),
-                    get(LocallyAvailableResourceFinder.class),
-                    get(ByUrlCachedExternalResourceIndex.class)
-                    );
-            return instantiator.newInstance(DefaultRepositoryHandler.class, resolverFactory, instantiator);
+            return instantiator.newInstance(DefaultRepositoryHandler.class, getResolverFactory(), instantiator);
         }
 
         public ConfigurationContainerInternal getConfigurationContainer() {
@@ -361,8 +371,7 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
                             get(PublishModuleDescriptorConverter.class),
                             fileModuleDescriptorConverter,
                             get(IvyFactory.class),
-                            new DefaultIvyDependencyPublisher(
-                                    new DefaultPublishOptionsFactory())));
+                            new DefaultIvyDependencyPublisher()));
         }
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
index c788fa9..15f5b61 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
@@ -20,7 +20,7 @@ import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.artifacts.ResolvedDependency;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
-import org.gradle.api.internal.file.FileSource;
+import org.gradle.internal.Factory;
 import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
@@ -31,10 +31,10 @@ import java.io.File;
 public class DefaultResolvedArtifact implements ResolvedArtifact {
     private final ResolvedDependency resolvedDependency;
     private final Artifact artifact;
-    private FileSource artifactSource;
+    private Factory<File> artifactSource;
     private File file;
 
-    public DefaultResolvedArtifact(ResolvedDependency resolvedDependency, Artifact artifact, FileSource artifactSource) {
+    public DefaultResolvedArtifact(ResolvedDependency resolvedDependency, Artifact artifact, Factory<File> artifactSource) {
         this.resolvedDependency = resolvedDependency;
         this.artifact = artifact;
         this.artifactSource = artifactSource;
@@ -104,7 +104,7 @@ public class DefaultResolvedArtifact implements ResolvedArtifact {
     
     public File getFile() {
         if (file == null) {
-            file = artifactSource.get();
+            file = artifactSource.create();
             artifactSource = null;
         }
         return file;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
index 08b7dc9..ceef34d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
@@ -20,7 +20,7 @@ import org.apache.commons.lang.StringUtils;
 import org.apache.tools.ant.Task;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java
index e75d408..61f749c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultCacheLockingManager.java
@@ -25,7 +25,7 @@ import org.gradle.cache.internal.FileLockManager;
 import java.io.File;
 
 public class DefaultCacheLockingManager implements CacheLockingManager {
-    public static final int CACHE_LAYOUT_VERSION = 13;
+    public static final int CACHE_LAYOUT_VERSION = 14;
     private final PersistentCache cache;
 
     public DefaultCacheLockingManager(CacheRepository cacheRepository) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
index 7803f6c..b189207 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
@@ -15,47 +15,148 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.event.publish.EndArtifactPublishEvent;
+import org.apache.ivy.core.event.publish.StartArtifactPublishEvent;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.MDArtifact;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.publish.PublishEngine;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.util.ConfigurationUtils;
 import org.gradle.api.UncheckedIOException;
-import org.gradle.util.WrapUtil;
+import org.gradle.util.DeprecationLogger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 
 /**
  * @author Hans Dockter
  */
 public class DefaultIvyDependencyPublisher implements IvyDependencyPublisher {
-    public static final String FILE_PATH_EXTRA_ATTRIBUTE = "filePath";
-    public static final List<String> ARTIFACT_PATTERN = WrapUtil.toList(String.format("[%s]", FILE_PATH_EXTRA_ATTRIBUTE));
+    public static final String FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE = "filePath";
 
     private static Logger logger = LoggerFactory.getLogger(DefaultIvyDependencyPublisher.class);
 
-    private PublishOptionsFactory publishOptionsFactory;
-
-    public DefaultIvyDependencyPublisher(PublishOptionsFactory publishOptionsFactory) {
-        this.publishOptionsFactory = publishOptionsFactory;
-    }
-
     public void publish(Set<String> configurations,
                         List<DependencyResolver> publishResolvers,
                         ModuleDescriptor moduleDescriptor,
                         File descriptorDestination,
-                        PublishEngine publishEngine) {
+                        EventManager eventManager) {
         try {
+            Publication publication = new Publication(moduleDescriptor, eventManager, configurations, descriptorDestination);
+
             for (DependencyResolver resolver : publishResolvers) {
-                logger.info("Publishing to Resolver {}", resolver);
-                publishEngine.publish(moduleDescriptor, ARTIFACT_PATTERN, resolver,
-                        publishOptionsFactory.createPublishOptions(configurations, descriptorDestination));
+                logger.info("Publishing to repository {}", resolver);
+                publication.publishTo(resolver);
             }
         } catch (IOException e) {
             throw new UncheckedIOException(e);
-        } 
+        }
+    }
+
+    private static class Publication {
+        private final ModuleDescriptor moduleDescriptor;
+        private final Set<String> configurations;
+        private final File descriptorFile;
+
+        private final EventManager eventManager;
+
+        private Publication(ModuleDescriptor moduleDescriptor, EventManager eventManager, Set<String> configurations, File descriptorFile) {
+            this.moduleDescriptor = moduleDescriptor;
+            this.eventManager = eventManager;
+            this.configurations = configurations;
+            this.descriptorFile = descriptorFile;
+        }
+
+        public void publishTo(DependencyResolver resolver) throws IOException {
+            Set<Artifact> allArtifacts = getAllArtifacts(moduleDescriptor);
+
+            Map<Artifact, File> artifactsFiles = new LinkedHashMap<Artifact, File>();
+            for (Artifact artifact : allArtifacts) {
+                addPublishedArtifact(artifact, artifactsFiles);
+            }
+            if (descriptorFile != null) {
+                addPublishedDescriptor(artifactsFiles);
+            }
+
+            boolean successfullyPublished = false;
+            try {
+                boolean overwrite = true;
+                resolver.beginPublishTransaction(moduleDescriptor.getModuleRevisionId(), overwrite);
+                // for each declared published artifact in this descriptor, do:
+                for (Map.Entry<Artifact, File> entry : artifactsFiles.entrySet()) {
+                    Artifact artifact = entry.getKey();
+                    File artifactFile = entry.getValue();
+                    publish(artifact, artifactFile, resolver, overwrite);
+                }
+                resolver.commitPublishTransaction();
+                successfullyPublished = true;
+            } finally {
+                if (!successfullyPublished) {
+                    resolver.abortPublishTransaction();
+                }
+            }
+        }
+
+        private void addPublishedDescriptor(Map<Artifact, File> artifactsFiles) {
+            Artifact artifact = MDArtifact.newIvyArtifact(moduleDescriptor);
+            checkArtifactFileExists(artifact, descriptorFile);
+            artifactsFiles.put(artifact, descriptorFile);
+        }
+
+        private void addPublishedArtifact(Artifact artifact, Map<Artifact, File> artifactsFiles) {
+            File artifactFile = new File(artifact.getExtraAttribute(FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE));
+            checkArtifactFileExists(artifact, artifactFile);
+            artifactsFiles.put(artifact, artifactFile);
+        }
+
+        private void checkArtifactFileExists(Artifact artifact, File artifactFile) {
+            if (!artifactFile.exists()) {
+                // TODO:DAZ This hack is required so that we don't log a warning when the Signing plugin is used. We need to allow conditional configurations so we can remove this.
+                if (isSigningArtifact(artifact)) {
+                    return;
+                }
+                String message = String.format("Attempted to publish an artifact that does not exist. File '%s' for artifact %s does not exist.\n"
+                        + "Relying on this behaviour is deprecated: in a future release of Gradle this build will fail.", artifactFile, artifact.getModuleRevisionId());
+                DeprecationLogger.nagUserWith(message);
+            }
+        }
+
+        private boolean isSigningArtifact(Artifact artifact) {
+            return artifact.getType().endsWith(".asc") || artifact.getType().endsWith(".sig");
+        }
+
+        private Set<Artifact> getAllArtifacts(ModuleDescriptor moduleDescriptor) {
+            Set<Artifact> allArtifacts = new LinkedHashSet<Artifact>();
+            String[] trueConfigurations = ConfigurationUtils.replaceWildcards(configurations.toArray(new String[configurations.size()]), moduleDescriptor);
+            for (String configuration : trueConfigurations) {
+                Collections.addAll(allArtifacts, moduleDescriptor.getArtifacts(configuration));
+            }
+            return allArtifacts;
+        }
+
+        private void publish(Artifact artifact, File src,
+                             DependencyResolver resolver, boolean overwrite) throws IOException {
+            IvyContext.getContext().checkInterrupted();
+            //notify triggers that an artifact is about to be published
+            eventManager.fireIvyEvent(
+                    new StartArtifactPublishEvent(resolver, artifact, src, overwrite));
+            boolean successful = false; //set to true once the publish succeeds
+            try {
+                if (src.exists()) {
+                    resolver.publish(artifact, src, overwrite);
+                    successful = true;
+                }
+            } finally {
+                //notify triggers that the publish is finished, successfully or not.
+                eventManager.fireIvyEvent(
+                        new EndArtifactPublishEvent(resolver, artifact, src, overwrite, successful));
+            }
+        }
     }
+
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
index c8c0107..2e5b7ca 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
@@ -20,6 +20,7 @@ import org.gradle.api.internal.CachingDirectedGraphWalker;
 import org.gradle.api.internal.DirectedGraphWithEdgeValues;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
 import org.gradle.api.specs.Spec;
+import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.util.*;
@@ -91,14 +92,51 @@ public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder
     }
 
     public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
-        return getFiles(dependencySpec, new LenientArtifactToFileResolver());
+        Set<ResolvedArtifact> artifacts = getArtifacts(dependencySpec);
+        return getFiles(artifacts);
     }
-    
+
     public Set<File> getFilesStrict(Spec<? super Dependency> dependencySpec) {
-        return getFiles(dependencySpec, new ArtifactFileResolver());
+        Set<ResolvedArtifact> artifacts = getAllArtifacts(dependencySpec);
+        return getFiles(artifacts);
+    }
+
+    /**
+     * Recursive but excludes unsuccessfully resolved artifacts.
+     *
+     * @param dependencySpec dependency spec
+     */
+    public Set<ResolvedArtifact> getArtifacts(Spec<? super Dependency> dependencySpec) {
+        Set<ResolvedArtifact> allArtifacts = getAllArtifacts(dependencySpec);
+        return CollectionUtils.filter(allArtifacts, new Spec<ResolvedArtifact>() {
+            public boolean isSatisfiedBy(ResolvedArtifact element) {
+                try {
+                    File file = element.getFile();
+                    return file != null;
+                } catch (ArtifactResolveException e) {
+                    return false;
+                }
+            }
+        });
     }
 
-    private Set<File> getFiles(Spec<? super Dependency> dependencySpec, ArtifactFileResolver artifactFileResolver) {
+    private Set<File> getFiles(Set<ResolvedArtifact> artifacts) {
+        Set<File> files = new LinkedHashSet<File>();
+        for (ResolvedArtifact artifact : artifacts) {
+            File depFile = artifact.getFile();
+            if (depFile != null) {
+                files.add(depFile);
+            }
+        }
+        return files;
+    }
+
+    /**
+     * Recursive, includes unsuccessfully resolved artifacts
+     *
+     * @param dependencySpec dependency spec
+     */
+    public Set<ResolvedArtifact> getAllArtifacts(Spec<? super Dependency> dependencySpec) {
         Set<ResolvedDependency> firstLevelModuleDependencies = getFirstLevelModuleDependencies(dependencySpec);
 
         Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
@@ -109,15 +147,7 @@ public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder
         }
 
         artifacts.addAll(walker.findValues());
-
-        Set<File> files = new LinkedHashSet<File>();
-        for (ResolvedArtifact artifact : artifacts) {
-            File depFile = artifactFileResolver.getFile(artifact);
-            if (depFile != null) {
-                files.add(depFile);
-            }
-        }
-        return files;
+        return artifacts;
     }
 
     private static class ResolvedDependencyArtifactsGraph implements DirectedGraphWithEdgeValues<ResolvedDependency, ResolvedArtifact> {
@@ -131,20 +161,4 @@ public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder
             values.addAll(to.getParentArtifacts(from));
         }
     }
-
-    private static class ArtifactFileResolver {
-        public File getFile(ResolvedArtifact artifact) {
-            return artifact.getFile();
-        }
-    }
-    
-    private static class LenientArtifactToFileResolver extends ArtifactFileResolver {
-        public File getFile(ResolvedArtifact artifact) {
-            try {
-                return super.getFile(artifact);
-            } catch (ArtifactResolveException e) {
-                return null;
-            }
-        }
-    }    
-}
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java
deleted file mode 100644
index e4d2fdb..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2007-2009 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 org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.publish.PublishOptions;
-
-import java.io.File;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultPublishOptionsFactory implements PublishOptionsFactory {
-    public PublishOptions createPublishOptions(Set<String> configurations, File descriptorDestination) {
-        PublishOptions publishOptions = createPublishOptions(configurations);
-        if (descriptorDestination != null) {
-            publishOptions.setSrcIvyPattern(descriptorDestination.getAbsolutePath());
-        }
-        return publishOptions;
-    }
-
-    private PublishOptions createPublishOptions(Set<String> configuration) {
-        PublishOptions publishOptions = new PublishOptions();
-        publishOptions.setOverwrite(true);
-        publishOptions.setConfs(configuration.toArray(new String[configuration.size()]));
-        return publishOptions;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
index fbc894b..2702701 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
@@ -15,19 +15,30 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.UnresolvedDependency;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.util.DeprecationLogger;
 
 public class DefaultUnresolvedDependency implements UnresolvedDependency {
-    private final String id;
     private final Throwable problem;
+    private final ModuleVersionSelector selector;
+    private final ModuleRevisionId revisionId;
 
-    public DefaultUnresolvedDependency(String id, Throwable problem) {
-        this.id = id;
+    public DefaultUnresolvedDependency(ModuleRevisionId id, Throwable problem) {
+        revisionId = id;
+        this.selector = new DefaultModuleVersionIdentifier(id.getOrganisation(), id.getName(), id.getRevision());
         this.problem = problem;
     }
 
     public String getId() {
-        return id;
+        DeprecationLogger.nagUserOfReplacedMethod("UnresolvedDependency.getId()", "UnresolvedDependency.getSelector()");
+        return revisionId.toString();
+    }
+
+    public ModuleVersionSelector getSelector() {
+        return selector;
     }
 
     public Throwable getProblem() {
@@ -35,6 +46,6 @@ public class DefaultUnresolvedDependency implements UnresolvedDependency {
     }
 
     public String toString() {
-        return id;
+        return selector.getGroup() + ":" + selector.getName() + ":" + selector.getVersion();
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
index 02624f5..bdb7e20 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
@@ -73,7 +73,7 @@ public class IvyBackedArtifactPublisher implements ArtifactPublisher {
                 publishResolvers,
                 publishModuleDescriptorConverter.convert(configurationsToPublish, configuration.getModule()),
                 descriptorDestination,
-                ivy.getPublishEngine());
+                ivy.getEventManager());
     }
 
     private void writeDescriptorFile(File descriptorDestination, Set<Configuration> configurationsToPublish, Module module) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
index 5fae794..650127c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
+import org.apache.ivy.core.event.EventManager;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.publish.PublishEngine;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 
 import java.io.File;
@@ -30,5 +30,5 @@ public interface IvyDependencyPublisher {
     void publish(Set<String> configurations,
                  List<DependencyResolver> publishResolvers,
                  ModuleDescriptor moduleDescriptor,
-                 File descriptorDestination, PublishEngine publishEngine);
+                 File descriptorDestination, EventManager eventManager);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java
deleted file mode 100644
index 8d1310f..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2007-2009 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 org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.publish.PublishOptions;
-
-import java.io.File;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public interface PublishOptionsFactory {
-    PublishOptions createPublishOptions(Set<String> configurations, File descriptorDestination);
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactory.java
index 5c96dcb..d0ec480 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolvedArtifactFactory.java
@@ -18,9 +18,8 @@ package org.gradle.api.internal.artifacts.ivyservice;
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.artifacts.ResolvedDependency;
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
-import org.gradle.api.internal.file.FileSource;
+import org.gradle.internal.Factory;
 
 import java.io.File;
 
@@ -32,8 +31,8 @@ public class ResolvedArtifactFactory {
     }
 
     public ResolvedArtifact create(ResolvedDependency owner, final Artifact artifact, final ArtifactResolver resolver) {
-        return new DefaultResolvedArtifact(owner, artifact, new FileSource() {
-            public File get() {
+        return new DefaultResolvedArtifact(owner, artifact, new Factory<File>() {
+            public File create() {
                 return lockingManager.useCache(String.format("download %s", artifact), new Factory<File>() {
                     public File create() {
                         return resolver.resolve(artifact).getFile();
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
index c46a926..13887c2 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
@@ -43,6 +43,10 @@ public class ShortcircuitEmptyConfigsArtifactDependencyResolver implements Artif
                 public Set<File> getFiles(Spec<? super Dependency> dependencySpec) {
                     return Collections.emptySet();
                 }
+
+                public Set<ResolvedArtifact> getArtifacts(Spec<? super Dependency> dependencySpec) {
+                    return Collections.emptySet();
+                }
             };
         }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
index caa5088..1451b0b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
 
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.Serializable;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
index e274041..860a8f0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ModuleResolutionCacheEntry.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
 
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.Serializable;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
index 1bd650c..ff89af8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/SingleFileBackedModuleResolutionCache.java
@@ -20,7 +20,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
index c8597d6..0922644 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
@@ -31,7 +31,7 @@ import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptor
 import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
 import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
index 28668b3..5e3aac1 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
@@ -30,7 +30,7 @@ import org.gradle.api.internal.externalresource.ivy.ArtifactAtRepositoryKey;
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
 import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 import org.gradle.util.WrapUtil;
 
 import java.util.List;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java
new file mode 100644
index 0000000..33b926d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParser.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
+import org.apache.ivy.plugins.repository.Resource;
+
+import java.io.IOException;
+import java.net.URL;
+import java.text.ParseException;
+
+public class DownloadedIvyModuleDescriptorParser extends XmlModuleDescriptorParser {
+    @Override
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res, boolean validate) throws ParseException, IOException {
+        DefaultModuleDescriptor descriptor = (DefaultModuleDescriptor) super.parseDescriptor(ivySettings, xmlURL, res, validate);
+        descriptor.setDefault(false);
+        return descriptor;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
index 9d7c602..49390e2 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
@@ -42,6 +42,7 @@ import org.apache.ivy.plugins.parser.m2.PomReader.PomDependencyData;
 import org.apache.ivy.plugins.repository.Resource;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.util.Message;
+import org.gradle.util.DeprecationLogger;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -232,37 +233,49 @@ public class GradlePomModuleDescriptorBuilder {
     }
 
     public void addMainArtifact(String artifactId, String packaging) {
-        String ext;
-
-        /*
-        * TODO: we should make packaging to ext mapping configurable, since it's not possible to cover all cases.
-        */
         if ("pom".equals(packaging)) {
-            // no artifact defined! Add the default artifact if it exist.
+            // no artifact defined! Add the default artifact only if it exists
             DependencyResolver resolver = parserSettings.getResolver(mrid);
 
             if (resolver != null) {
-                DefaultArtifact artifact = new DefaultArtifact(
-                        mrid, new Date(), artifactId, "jar", "jar");
-                ArtifactOrigin artifactOrigin = resolver.locate(artifact);
+                DefaultArtifact artifact = new DefaultArtifact(mrid, new Date(), artifactId, "jar", "jar");
 
-                if (!ArtifactOrigin.isUnknown(artifactOrigin)) {
+                if (!ArtifactOrigin.isUnknown(resolver.locate(artifact))) {
                     mainArtifact = artifact;
                     ivyModuleDescriptor.addArtifact("master", mainArtifact);
                 }
             }
 
             return;
-        } else if (JAR_PACKAGINGS.contains(packaging)) {
-            ext = "jar";
-        } else {
-            ext = packaging;
         }
 
-        mainArtifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, ext);
+        if (!isKnownJarPackaging(packaging)) {
+            // Look for an artifact with extension = packaging. This is deprecated.
+            DependencyResolver resolver = parserSettings.getResolver(mrid);
+
+            if (resolver != null) {
+                DefaultArtifact artifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, packaging);
+
+                if (!ArtifactOrigin.isUnknown(resolver.locate(artifact))) {
+                    mainArtifact = artifact;
+                    ivyModuleDescriptor.addArtifact("master", mainArtifact);
+
+                    DeprecationLogger.nagUserWith("Deprecated: relying on packaging to define the extension of the main artifact is deprecated, "
+                            + "and will not be supported in a future version of Gradle.");
+
+                    return;
+                }
+            }
+        }
+
+        mainArtifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, "jar");
         ivyModuleDescriptor.addArtifact("master", mainArtifact);
     }
 
+    private boolean isKnownJarPackaging(String packaging) {
+        return "jar".equals(packaging) || JAR_PACKAGINGS.contains(packaging);
+    }
+
     public void addDependency(PomDependencyData dep) {
         String scope = dep.getScope();
         if ((scope != null) && (scope.length() > 0) && !MAVEN2_CONF_MAPPING.containsKey(scope)) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
index a32f4b3..02c5d65 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
@@ -49,17 +49,6 @@ import java.util.Map;
  * number of remote call in half to resolve a module.
  */
 public final class GradlePomModuleDescriptorParser implements ModuleDescriptorParser {
-
-    private static final GradlePomModuleDescriptorParser INSTANCE = new GradlePomModuleDescriptorParser();
-
-    public static GradlePomModuleDescriptorParser getInstance() {
-        return INSTANCE;
-    }
-
-    private GradlePomModuleDescriptorParser() {
-    }
-
-
     public void toIvyFile(InputStream is, Resource res, File destFile, ModuleDescriptor md)
             throws ParseException, IOException {
         try {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java
index 3f83ffa..efd9040 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ParserRegistry.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
 
 import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
-import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
 import org.apache.ivy.plugins.repository.Resource;
 
 import java.util.ArrayList;
@@ -26,8 +25,8 @@ public class ParserRegistry {
     private List<ModuleDescriptorParser> parsers = new ArrayList<ModuleDescriptorParser>();
 
     public ParserRegistry() {
-        parsers.add(GradlePomModuleDescriptorParser.getInstance());
-        parsers.add(XmlModuleDescriptorParser.getInstance());
+        parsers.add(new GradlePomModuleDescriptorParser());
+        parsers.add(new DownloadedIvyModuleDescriptorParser());
     }
 
     public ModuleDescriptorParser forResource(Resource resource) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
index f959167..e16c5d0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultCachedModuleDescriptor.java
@@ -19,7 +19,7 @@ import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.Serializable;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
index 10b49a1..e86c418 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
@@ -21,7 +21,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ArtifactCacheMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionRepository;
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
index f510a23..a26b8e8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleDescriptorCacheEntry.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.modulecache;
 
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.Serializable;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
index b3f4f87..48000c1 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
@@ -41,7 +41,7 @@ public class DefaultArtifactsToModuleDescriptorConverter implements ArtifactsToM
 
     public final static ArtifactsExtraAttributesStrategy RESOLVE_STRATEGY = new ArtifactsExtraAttributesStrategy() {
         public Map<String, String> createExtraAttributes(PublishArtifact publishArtifact) {
-            return WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
+            return WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
         }
     };
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
index c628080..9e928b6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
@@ -46,9 +46,9 @@ public class DefaultProjectModuleRegistry implements ProjectModuleRegistry {
             for (Artifact artifact : projectDescriptor.getAllArtifacts()) {
                 if (artifact.getName().equals(artifactDescriptor.getName()) && artifact.getExt().equals(
                         artifactDescriptor.getExt())) {
-                    String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
+                    String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE);
                     ReflectionUtil.invoke(artifactDescriptor, "setExtraAttribute",
-                            new Object[]{DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, path});
+                            new Object[]{DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE, path});
                 }
             }
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
index 2705395..fcd7e5f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
@@ -47,7 +47,7 @@ public class ProjectDependencyResolver implements DependencyToModuleResolver {
 
     private static class ProjectArtifactResolver implements ArtifactResolver {
         public ArtifactResolveResult resolve(Artifact artifact) throws ArtifactResolveException {
-            String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
+            String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE);
             return new FileBackedArtifactResolveResult(new File(path));
         }
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
index 0d57a0b..73865e1 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
@@ -154,7 +154,7 @@ public class DependencyGraphBuilder {
         public void attachFailures(ResolvedConfigurationBuilder result) {
             for (Map.Entry<ModuleRevisionId, BrokenDependency> entry : failuresByRevisionId.entrySet()) {
                 Collection<List<ModuleRevisionId>> paths = calculatePaths(entry);
-                result.addUnresolvedDependency(new DefaultUnresolvedDependency(entry.getKey().toString(), entry.getValue().failure.withIncomingPaths(paths)));
+                result.addUnresolvedDependency(new DefaultUnresolvedDependency(entry.getKey(), entry.getValue().failure.withIncomingPaths(paths)));
             }
         }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java
index a801d4a..5be1816 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/CannotLocateLocalMavenRepositoryException.java
@@ -16,9 +16,13 @@
 
 package org.gradle.api.internal.artifacts.mvnsettings;
 
-public class CannotLocateLocalMavenRepositoryException extends RuntimeException {
-    public CannotLocateLocalMavenRepositoryException(Throwable cause) {
-        super(cause);
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.Contextual;
+
+ at Contextual
+public class CannotLocateLocalMavenRepositoryException extends GradleException {
+    public CannotLocateLocalMavenRepositoryException(String message, Throwable cause) {
+        super(message, cause);
     }
 
     public CannotLocateLocalMavenRepositoryException(String message) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java
index 6c57b88..5d43ac6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocator.java
@@ -15,15 +15,13 @@
  */
 package org.gradle.api.internal.artifacts.mvnsettings;
 
-import org.apache.maven.settings.DefaultMavenSettingsBuilder;
-import org.apache.maven.settings.MavenSettingsBuilder;
-import org.apache.maven.settings.Settings;
-import org.gradle.api.internal.artifacts.PlexusLoggerAdapter;
+import jarjar.org.apache.maven.settings.Settings;
+import jarjar.org.apache.maven.settings.building.*;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.lang.reflect.Field;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -45,16 +43,23 @@ public class DefaultLocalMavenRepositoryLocator implements LocalMavenRepositoryL
         this.environmentVariables = environmentVariables;
     }
 
-    public File getLocalMavenRepository() {
-        Settings settings = buildSettings();
-        String repoPath = settings.getLocalRepository().trim();
-        return new File(resolvePlaceholders(repoPath));
+    public File getLocalMavenRepository() throws CannotLocateLocalMavenRepositoryException{
+        try {
+            Settings settings = buildSettings();
+            String repoPath = settings.getLocalRepository();
+            if (repoPath == null) {
+                repoPath = new File(System.getProperty("user.home"), "/.m2/repository").getAbsolutePath();
+                LOGGER.debug(String.format("No local repository in Settings file defined. Using default path: %s", repoPath));
+            }
+            return new File(resolvePlaceholders(repoPath.trim()));
+        } catch (SettingsBuildingException e) {
+            throw new CannotLocateLocalMavenRepositoryException("Unable to parse local maven settings", e);
+        }
     }
 
     private String resolvePlaceholders(String value) {
         StringBuffer result = new StringBuffer();
         Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
-
         while (matcher.find()) {
             String placeholder = matcher.group(1);
             String replacement = placeholder.startsWith("env.") ? environmentVariables.get(placeholder.substring(4)) : systemProperties.get(placeholder);
@@ -68,26 +73,16 @@ public class DefaultLocalMavenRepositoryLocator implements LocalMavenRepositoryL
         return result.toString();
     }
 
-    private Settings buildSettings() {
-        try {
-            return createSettingsBuilder().buildSettings();
-        } catch (Exception e) {
-            throw new CannotLocateLocalMavenRepositoryException(e);
-        }
-    }
-
-    private MavenSettingsBuilder createSettingsBuilder() throws Exception {
-        DefaultMavenSettingsBuilder builder = new DefaultMavenSettingsBuilder();
-        builder.enableLogging(new PlexusLoggerAdapter(LOGGER));
-
-        Field userSettingsFileField = DefaultMavenSettingsBuilder.class.getDeclaredField("userSettingsFile");
-        userSettingsFileField.setAccessible(true);
-        userSettingsFileField.set(builder, mavenFileLocations.getUserSettingsFile());
-
-        Field globalSettingsFileField = DefaultMavenSettingsBuilder.class.getDeclaredField("globalSettingsFile");
-        globalSettingsFileField.setAccessible(true);
-        globalSettingsFileField.set(builder, mavenFileLocations.getGlobalSettingsFile());
 
-        return builder;
+    private Settings buildSettings() throws SettingsBuildingException {
+        DefaultSettingsBuilderFactory factory = new DefaultSettingsBuilderFactory();
+        DefaultSettingsBuilder defaultSettingsBuilder = factory.newInstance();
+        DefaultSettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest();
+        settingsBuildingRequest.setSystemProperties(System.getProperties());
+        settingsBuildingRequest.setUserSettingsFile(mavenFileLocations.getUserMavenDir());
+        settingsBuildingRequest.setUserSettingsFile(mavenFileLocations.getUserSettingsFile());
+        settingsBuildingRequest.setGlobalSettingsFile(mavenFileLocations.getGlobalSettingsFile());
+        SettingsBuildingResult settingsBuildingResult = defaultSettingsBuilder.build(settingsBuildingRequest);
+        return settingsBuildingResult.getEffectiveSettings();
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenFileLocations.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenFileLocations.java
index 86592c6..6bde2b9 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenFileLocations.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultMavenFileLocations.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.mvnsettings;
 
 import org.gradle.api.Nullable;
 import org.gradle.internal.SystemProperties;
+import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
 
@@ -31,9 +32,13 @@ public class DefaultMavenFileLocations implements MavenFileLocations {
 
     @Nullable
     public File getGlobalMavenDir() {
-        String m2Home = System.getProperty("M2_HOME");
+        String m2Home = System.getenv("M2_HOME");
         if (m2Home == null) {
-            return null;
+            m2Home = System.getProperty("M2_HOME");
+            if(m2Home==null){
+                return null;
+            }
+            DeprecationLogger.nagUserWith("Found defined M2_HOME system property. Handling M2_HOME system property is deprecated in Gradle and will be removed in the next version of Gradle. Please use a M2_HOME environment variable instead.");
         }
         return new File(m2Home);
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultExternalResourceRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultExternalResourceRepository.java
index 34576b6..e86e296 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultExternalResourceRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultExternalResourceRepository.java
@@ -17,16 +17,22 @@
 package org.gradle.api.internal.artifacts.repositories;
 
 
+import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.plugins.repository.AbstractRepository;
 import org.apache.ivy.plugins.repository.BasicResource;
 import org.apache.ivy.plugins.repository.RepositoryCopyProgressListener;
 import org.apache.ivy.plugins.repository.TransferEvent;
-import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
 import org.gradle.api.internal.externalresource.ExternalResource;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 import org.gradle.api.internal.externalresource.transfer.*;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.file.TmpDirTemporaryFileProvider;
 import org.gradle.internal.UncheckedException;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.hash.HashUtil;
+import org.gradle.util.hash.HashValue;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -38,6 +44,7 @@ public class DefaultExternalResourceRepository extends AbstractRepository implem
 
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExternalResourceRepository.class);
     private final RepositoryCopyProgressListener progress = new RepositoryCopyProgressListener(this);
+    private final TemporaryFileProvider temporaryFileProvider = new TmpDirTemporaryFileProvider();
 
     private final ExternalResourceAccessor accessor;
     private final ExternalResourceUploader uploader;
@@ -88,6 +95,25 @@ public class DefaultExternalResourceRepository extends AbstractRepository implem
     }
 
     @Override
+    public void put(Artifact artifact, File source, String destination, boolean overwrite) throws IOException {
+        put(source, destination, overwrite);
+        putChecksum("SHA1", source, destination, overwrite);
+    }
+
+    private void putChecksum(String algorithm, File source, String destination, boolean overwrite) throws IOException {
+        File checksumFile = createChecksumFile(source, algorithm);
+        String checksumDestination = destination + "." + algorithm.toLowerCase();
+        put(checksumFile, checksumDestination, overwrite);
+    }
+
+    private File createChecksumFile(File src, String algorithm) {
+        File csFile = temporaryFileProvider.createTemporaryFile("ivytemp", algorithm);
+        HashValue hash = HashUtil.createHash(src, algorithm);
+        GFileUtils.writeFile(hash.asHexString(), csFile);
+        return csFile;
+    }
+
+    @Override
     protected void put(File source, String destination, boolean overwrite) throws IOException {
         LOGGER.debug("Attempting to put resource {}.", destination);
         assert source.isFile();
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java
index 4a8a8ab..378958a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactory.java
@@ -21,15 +21,16 @@ import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.repositories.*;
-import org.gradle.api.internal.Instantiator;
 import org.gradle.api.internal.artifacts.ResolverFactory;
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex;
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 
+import java.io.File;
 import java.util.Map;
 
 /**
@@ -86,7 +87,8 @@ public class DefaultResolverFactory implements ResolverFactory {
 
     public MavenArtifactRepository createMavenLocalRepository() {
         MavenArtifactRepository mavenRepository = createMavenRepository();
-        mavenRepository.setUrl(localMavenRepositoryLocator.getLocalMavenRepository());
+        final File localMavenRepository = localMavenRepositoryLocator.getLocalMavenRepository();
+        mavenRepository.setUrl(localMavenRepository);
         return mavenRepository;
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceResolver.java
index 68a8732..3481dbb 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ExternalResourceResolver.java
@@ -204,11 +204,7 @@ public class ExternalResourceResolver extends BasicResolver {
                     continue;
                 }
                 ModuleDescriptor md = parsedResource.getResolvedModuleRevision().getDescriptor();
-                if (md.isDefault()) {
-                    LOGGER.debug(name + ": default md rejected by version matcher requiring module descriptor: " + description);
-                    discardResource(resource);
-                    continue;
-                } else if (!versionMatcher.accept(mrid, md)) {
+                if (!versionMatcher.accept(mrid, md)) {
                     LOGGER.debug(name + ": md rejected by version matcher: " + description);
                     discardResource(resource);
                     continue;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/file/FileExternalResourceRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/file/FileExternalResourceRepository.java
deleted file mode 100644
index a85cd58..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/transport/file/FileExternalResourceRepository.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal.artifacts.repositories.transport.file;
-
-import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.plugins.repository.file.FileRepository;
-import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
-import org.gradle.api.internal.externalresource.cached.CachedExternalResource;
-import org.gradle.api.internal.externalresource.ExternalResource;
-import org.gradle.api.internal.externalresource.ExternalResourceIvyResourceAdapter;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceCandidates;
-
-import java.io.File;
-import java.io.IOException;
-
-public class FileExternalResourceRepository extends FileRepository implements ExternalResourceRepository {
-
-    public void downloadResource(ExternalResource resource, File destination) throws IOException {
-        get(resource.getName(), destination);
-    }
-
-    public ExternalResource getResource(String source, LocallyAvailableResourceCandidates localCandidates, CachedExternalResource cached) throws IOException {
-        return getResource(source);
-    }
-
-    public ExternalResourceMetaData getResourceMetaData(String source) throws IOException {
-        Resource resource = getResource(source);
-        return resource == null || !resource.exists() ? null : new ExternalResourceIvyResourceAdapter(resource).getMetaData();
-    }
-
-    public ExternalResource getResource(String source) throws IOException {
-        return new ExternalResourceIvyResourceAdapter(super.getResource(source));
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/AbstractExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/AbstractExternalResource.java
index fe7ade2..833a60b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/AbstractExternalResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/AbstractExternalResource.java
@@ -19,26 +19,25 @@ import org.apache.ivy.plugins.repository.Resource;
 import org.apache.ivy.util.CopyProgressListener;
 import org.apache.ivy.util.FileUtil;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
 
 public abstract class AbstractExternalResource implements ExternalResource {
     public void writeTo(File destination, CopyProgressListener progress) throws IOException {
         FileOutputStream output = new FileOutputStream(destination);
+        writeTo(output, progress);
+        output.close();
+    }
+
+    public void writeTo(OutputStream output, CopyProgressListener progress) throws IOException {
+        InputStream input = openStream();
         try {
-            InputStream input = openStream();
-            try {
-                FileUtil.copy(input, output, progress);
-            } finally {
-                input.close();
-            }
+            FileUtil.copy(input, output, progress);
         } finally {
-            output.close();
+            input.close();
         }
     }
 
+
     public Resource clone(String cloneName) {
         throw new UnsupportedOperationException();
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java
index d606a94..983795a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResource.java
@@ -22,10 +22,13 @@ import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaDat
 
 import java.io.File;
 import java.io.IOException;
+import java.io.OutputStream;
 
 public interface ExternalResource extends Resource {
     void writeTo(File destination, CopyProgressListener progress) throws IOException;
 
+    void writeTo(OutputStream destination, CopyProgressListener progress) throws IOException;
+
     void close() throws IOException;
 
     @Nullable
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResourceIvyResourceAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResourceIvyResourceAdapter.java
deleted file mode 100644
index 91fc842..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ExternalResourceIvyResourceAdapter.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.api.internal.externalresource;
-
-import org.apache.ivy.plugins.repository.Resource;
-import org.apache.ivy.util.CopyProgressListener;
-import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData;
-import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class ExternalResourceIvyResourceAdapter implements ExternalResource {
-
-    private final Resource delegate;
-
-    public ExternalResourceIvyResourceAdapter(Resource delegate) {
-        this.delegate = delegate;
-    }
-
-    public void writeTo(File destination, CopyProgressListener progress) throws IOException {
-        throwUnsupported();
-    }
-
-    public void close() throws IOException {
-        // noop
-    }
-
-    private void throwUnsupported() {
-        throw new UnsupportedOperationException("ExternalResourceIvyResourceAdapter does not support methods added by ExternalResource");
-    }
-
-    public String getName() {
-        return delegate.getName();
-    }
-
-    public long getLastModified() {
-        return delegate.getLastModified();
-    }
-
-    public long getContentLength() {
-        return delegate.getContentLength();
-    }
-
-    public boolean exists() {
-        return delegate.exists();
-    }
-
-    public boolean isLocal() {
-        return delegate.isLocal();
-    }
-
-    public Resource clone(String cloneName) {
-        return delegate.clone(cloneName);
-    }
-
-    public InputStream openStream() throws IOException {
-        return delegate.openStream();
-    }
-
-    public ExternalResourceMetaData getMetaData() {
-        return new DefaultExternalResourceMetaData(delegate.getName(), getLastModified(), getContentLength(), null, null);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java
index 4ed9fdd..dea8be3 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/LocalFileStandInExternalResource.java
@@ -38,10 +38,6 @@ public class LocalFileStandInExternalResource extends AbstractExternalResource {
     private HashValue sha1;
     private ExternalResourceMetaData metaData;
 
-    public LocalFileStandInExternalResource(String source, File localFile) {
-        this(source, localFile, null);
-    }
-
     public LocalFileStandInExternalResource(String source, File localFile, ExternalResourceMetaData metaData) {
         this.source = source;
         this.localFile = localFile;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java
index c64b00c..5dc7061 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/ByUrlCachedExternalResourceIndex.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.externalresource.cached;
 
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.File;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
index 0007b6b..dbf56c3 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/cached/DefaultCachedExternalResourceIndex.java
@@ -20,7 +20,7 @@ import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
 import org.gradle.cache.PersistentIndexedCache;
 import org.gradle.internal.Factory;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.File;
 import java.io.Serializable;
@@ -92,7 +92,7 @@ public class DefaultCachedExternalResourceIndex<K extends Serializable> implemen
             throw new IllegalArgumentException("key cannot be null");
         }
 
-        return cacheLockingManager.useCache(operationName("store"), new Factory<DefaultCachedExternalResource>() {
+        return cacheLockingManager.useCache(operationName("lookup"), new Factory<DefaultCachedExternalResource>() {
             public DefaultCachedExternalResource create() {
                 DefaultCachedExternalResource found = getPersistentCache().get(key);
                 if (found == null) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java
index 2b9dba1..0fd2ea8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/ivy/ArtifactAtRepositoryCachedExternalResourceIndex.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal.externalresource.ivy;
 
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 import org.gradle.api.internal.externalresource.cached.DefaultCachedExternalResourceIndex;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.File;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocalMavenLocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocalMavenLocallyAvailableResourceFinder.java
new file mode 100644
index 0000000..538dfdd
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocalMavenLocallyAvailableResourceFinder.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.externalresource.local.ivy;
+
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.Transformer;
+import org.gradle.api.internal.artifacts.mvnsettings.CannotLocateLocalMavenRepositoryException;
+import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator;
+import org.gradle.api.internal.externalresource.local.AbstractLocallyAvailableResourceFinder;
+import org.gradle.internal.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+public class LocalMavenLocallyAvailableResourceFinder extends AbstractLocallyAvailableResourceFinder<ArtifactRevisionId> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(LocalMavenLocallyAvailableResourceFinder.class);
+
+    public LocalMavenLocallyAvailableResourceFinder(LocalMavenRepositoryLocator localMavenRepositoryLocator, String pattern) {
+        super(createProducer(localMavenRepositoryLocator, pattern));
+    }
+
+    private static Transformer<Factory<List<File>>, ArtifactRevisionId> createProducer(final LocalMavenRepositoryLocator localMavenRepositoryLocator, final String pattern) {
+        return new LazyLocalMavenPatternTransformer(localMavenRepositoryLocator, pattern);
+    }
+
+    private static class LazyLocalMavenPatternTransformer implements Transformer<Factory<List<File>>, ArtifactRevisionId> {
+        private final LocalMavenRepositoryLocator localMavenRepositoryLocator;
+        private final String pattern;
+
+        private PatternTransformer patternTransformer;
+        private boolean initialized;
+
+        public LazyLocalMavenPatternTransformer(LocalMavenRepositoryLocator localMavenRepositoryLocator, String pattern) {
+            this.localMavenRepositoryLocator = localMavenRepositoryLocator;
+            this.pattern = pattern;
+        }
+
+        public Factory<List<File>> transform(ArtifactRevisionId original) {
+            if (!initialized) {
+                initializedPatternTransformer();
+            }
+
+            if (patternTransformer != null) {
+                return patternTransformer.transform(original);
+            } else {
+                return new Factory<List<File>>() {
+                    public List<File> create() {
+                        return Collections.emptyList();
+                    }
+                };
+            }
+        }
+
+        private void initializedPatternTransformer() {
+            try {
+                File localMavenRepository = localMavenRepositoryLocator.getLocalMavenRepository();
+                this.patternTransformer = new PatternTransformer(localMavenRepository, pattern);
+            } catch (CannotLocateLocalMavenRepositoryException ex) {
+                LOGGER.warn(String.format("Unable to locate local Maven repository."));
+                LOGGER.debug(String.format("Problems while locating local maven repository.", ex));
+            } finally {
+                initialized = true;
+            }
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
index dd725f9..104c485 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/LocallyAvailableResourceFinderFactory.java
@@ -23,12 +23,16 @@ import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFi
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinderSearchableFileStoreAdapter;
 import org.gradle.api.internal.filestore.FileStoreSearcher;
 import org.gradle.internal.Factory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.util.LinkedList;
 import java.util.List;
 
 public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAvailableResourceFinder<ArtifactRevisionId>> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(LocallyAvailableResourceFinderFactory.class);
+
     private final File rootCachesDirectory;
     private final LocalMavenRepositoryLocator localMavenRepositoryLocator;
     private final FileStoreSearcher<ArtifactRevisionId> fileStore;
@@ -44,13 +48,16 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
         List<LocallyAvailableResourceFinder<ArtifactRevisionId>> finders = new LinkedList<LocallyAvailableResourceFinder<ArtifactRevisionId>>();
 
         // Order is important here, because they will be searched in that order
-        
+
         // The current filestore
         finders.add(new LocallyAvailableResourceFinderSearchableFileStoreAdapter<ArtifactRevisionId>(fileStore));
-        
+
+        // rc-1, 1.0
+        addForPattern(finders, "artifacts-13", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
+
         // Milestone 8 and 9
         addForPattern(finders, "artifacts-8", "filestore/[organisation]/[module](/[branch])/[revision]/[type]/*/[artifact]-[revision](-[classifier])(.[ext])");
-        
+
         // Milestone 7
         addForPattern(finders, "artifacts-7", "artifacts/*/[organisation]/[module](/[branch])/[revision]/[type]/[artifact]-[revision](-[classifier])(.[ext])");
 
@@ -61,8 +68,8 @@ public class LocallyAvailableResourceFinderFactory implements Factory<LocallyAva
         // Milestone 3
         addForPattern(finders, "../cache", "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])");
 
-        // Maven local
-        addForPattern(finders, localMavenRepositoryLocator.getLocalMavenRepository(), "[organisation-path]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])");
+        // local maven
+        finders.add(new LocalMavenLocallyAvailableResourceFinder(localMavenRepositoryLocator, "[organisation-path]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])"));
 
         return new CompositeLocallyAvailableResourceFinder<ArtifactRevisionId>(finders);
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java
index f79aaf1..b9202b8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternBasedLocallyAvailableResourceFinder.java
@@ -15,67 +15,13 @@
  */
 package org.gradle.api.internal.externalresource.local.ivy;
 
-import org.apache.ivy.core.IvyPatternHelper;
-import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DefaultArtifact;
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
-import org.gradle.api.Transformer;
-import org.gradle.api.file.EmptyFileVisitor;
-import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.internal.externalresource.local.AbstractLocallyAvailableResourceFinder;
-import org.gradle.api.internal.file.collections.DirectoryFileTree;
-import org.gradle.api.tasks.util.PatternFilterable;
-import org.gradle.api.tasks.util.PatternSet;
-import org.gradle.internal.Factory;
 
 import java.io.File;
-import java.util.LinkedList;
-import java.util.List;
 
 public class PatternBasedLocallyAvailableResourceFinder extends AbstractLocallyAvailableResourceFinder<ArtifactRevisionId> {
-
     public PatternBasedLocallyAvailableResourceFinder(File baseDir, String pattern) {
-        super(createProducer(baseDir, pattern));
-    }
-
-    private static Transformer<Factory<List<File>>, ArtifactRevisionId> createProducer(final File baseDir, final String pattern) {
-        return new Transformer<Factory<List<File>>, ArtifactRevisionId>() {
-            DirectoryFileTree fileTree = new DirectoryFileTree(baseDir);
-
-            private String getArtifactPattern(ArtifactRevisionId artifactId) {
-                String substitute = pattern;
-                // Need to handle organisation values that have been munged for m2compatible
-                substitute = IvyPatternHelper.substituteToken(substitute, "organisation", artifactId.getModuleRevisionId().getOrganisation().replace('/', '.'));
-                substitute = IvyPatternHelper.substituteToken(substitute, "organisation-path", artifactId.getModuleRevisionId().getOrganisation().replace('.', '/'));
-
-                Artifact dummyArtifact = new DefaultArtifact(artifactId, null, null, false);
-                substitute = IvyPatternHelper.substitute(substitute, dummyArtifact);
-                return substitute;
-            }
-
-            private DirectoryFileTree getMatchingFiles(ArtifactRevisionId artifact) {
-                String patternString = getArtifactPattern(artifact);
-                PatternFilterable pattern = new PatternSet();
-                pattern.include(patternString);
-                return fileTree.filter(pattern);
-            }
-
-            public Factory<List<File>> transform(final ArtifactRevisionId artifactId) {
-                return new Factory<List<File>>() {
-                    public List<File> create() {
-                        final List<File> files = new LinkedList<File>();
-                        if (artifactId != null) {
-                            getMatchingFiles(artifactId).visit(new EmptyFileVisitor() {
-                                public void visitFile(FileVisitDetails fileDetails) {
-                                    files.add(fileDetails.getFile());
-                                }
-                            });
-                        }
-                        return files;
-                    }
-                };
-            }
-        };
+        super(new PatternTransformer(baseDir, pattern));
     }
-
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternTransformer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternTransformer.java
new file mode 100644
index 0000000..8baba0f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/local/ivy/PatternTransformer.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.externalresource.local.ivy;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ArtifactRevisionId;
+import org.gradle.api.Transformer;
+import org.gradle.api.file.EmptyFileVisitor;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.internal.file.collections.DirectoryFileTree;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.Factory;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+public class PatternTransformer implements Transformer<Factory<List<File>>, ArtifactRevisionId> {
+
+    private final String pattern;
+    private DirectoryFileTree fileTree;
+
+    public PatternTransformer(File baseDir, String pattern) {
+        this.pattern = pattern;
+        fileTree = new DirectoryFileTree(baseDir);
+    }
+
+    private String getArtifactPattern(ArtifactRevisionId artifactId) {
+        String substitute = pattern;
+        // Need to handle organisation values that have been munged for m2compatible
+        substitute = IvyPatternHelper.substituteToken(substitute, "organisation", artifactId.getModuleRevisionId().getOrganisation().replace('/', '.'));
+        substitute = IvyPatternHelper.substituteToken(substitute, "organisation-path", artifactId.getModuleRevisionId().getOrganisation().replace('.', '/'));
+
+        Artifact dummyArtifact = new DefaultArtifact(artifactId, null, null, false);
+        substitute = IvyPatternHelper.substitute(substitute, dummyArtifact);
+        return substitute;
+    }
+
+    private DirectoryFileTree getMatchingFiles(ArtifactRevisionId artifact) {
+        String patternString = getArtifactPattern(artifact);
+        PatternFilterable pattern = new PatternSet();
+        pattern.include(patternString);
+        return fileTree.filter(pattern);
+    }
+
+    public Factory<List<File>> transform(final ArtifactRevisionId artifactId) {
+        return new Factory<List<File>>() {
+            public List<File> create() {
+                final List<File> files = new LinkedList<File>();
+                if (artifactId != null) {
+                    getMatchingFiles(artifactId).visit(new EmptyFileVisitor() {
+                        public void visitFile(FileVisitDetails fileDetails) {
+                            files.add(fileDetails.getFile());
+                        }
+                    });
+                }
+                return files;
+            }
+        };
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompare.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompare.java
index b9e3f7a..10921a3 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompare.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/metadata/ExternalResourceMetaDataCompare.java
@@ -18,15 +18,10 @@ package org.gradle.api.internal.externalresource.metadata;
 
 import org.gradle.api.Nullable;
 import org.gradle.internal.Factory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Date;
 
 public abstract class ExternalResourceMetaDataCompare {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(ExternalResourceMetaDataCompare.class);
-
     public static boolean isDefinitelyUnchanged(@Nullable ExternalResourceMetaData local, Factory<ExternalResourceMetaData> remoteFactory) {
         if (local == null) {
             return false;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java
new file mode 100644
index 0000000..2af21dc
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileResourceConnector.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.externalresource.transport.file;
+
+import org.apache.ivy.util.FileUtil;
+import org.gradle.api.internal.externalresource.ExternalResource;
+import org.gradle.api.internal.externalresource.LocallyAvailableExternalResource;
+import org.gradle.api.internal.externalresource.local.DefaultLocallyAvailableResource;
+import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceAccessor;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceLister;
+import org.gradle.api.internal.externalresource.transfer.ExternalResourceUploader;
+import org.gradle.util.hash.HashValue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FileResourceConnector implements ExternalResourceLister, ExternalResourceAccessor, ExternalResourceUploader {
+
+    public List<String> list(String parent) throws IOException {
+        File dir = getFile(parent);
+        if (dir.exists() && dir.isDirectory()) {
+            String[] names = dir.list();
+            if (names != null) {
+                List<String> ret = new ArrayList<String>(names.length);
+                for (String name : names) {
+                    ret.add(parent + '/' + name);
+                }
+                return ret;
+            }
+        }
+        return null;
+    }
+
+    public void upload(File source, String destination, boolean overwrite) throws IOException {
+        File target = getFile(destination);
+        if (!overwrite && target.exists()) {
+            throw new IOException("Could not copy file " + source + " to " + target + ": target already exists and overwrite is false");
+        }
+        if (!FileUtil.copy(source, target, null, overwrite)) {
+            throw new IOException("File copy failed from " + source + " to " + target);
+        }
+    }
+
+    public ExternalResource getResource(String location) throws IOException {
+        File localFile = getFile(location);
+        if (!localFile.exists()) {
+            return null;
+        }
+        return new LocallyAvailableExternalResource(location, new DefaultLocallyAvailableResource(localFile));
+    }
+
+    public ExternalResourceMetaData getMetaData(String location) throws IOException {
+        ExternalResource resource = getResource(location);
+        return resource == null ? null : resource.getMetaData();
+    }
+
+    public HashValue getResourceSha1(String location) {
+        // TODO Read sha1 from published .sha1 file
+        return null;
+    }
+
+    private static File getFile(String absolutePath) {
+        File f = new File(absolutePath);
+        if (!f.isAbsolute()) {
+            throw new IllegalArgumentException("Filename must be absolute: " + absolutePath);
+        }
+        return f;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
index 9dcd893..bf15590 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/file/FileTransport.java
@@ -17,9 +17,9 @@ package org.gradle.api.internal.externalresource.transport.file;
 
 import org.apache.ivy.core.cache.RepositoryCacheManager;
 import org.apache.ivy.plugins.resolver.AbstractResolver;
+import org.gradle.api.internal.artifacts.repositories.DefaultExternalResourceRepository;
 import org.gradle.api.internal.artifacts.repositories.ExternalResourceRepository;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
-import org.gradle.api.internal.artifacts.repositories.transport.file.FileExternalResourceRepository;
 
 import java.io.File;
 import java.net.URI;
@@ -34,9 +34,8 @@ public class FileTransport implements RepositoryTransport {
     }
 
     public ExternalResourceRepository getRepository() {
-        FileExternalResourceRepository fileRepository = new FileExternalResourceRepository();
-        fileRepository.setName(name);
-        return fileRepository;
+        FileResourceConnector connector = new FileResourceConnector();
+        return new DefaultExternalResourceRepository(name, connector, connector, connector);
     }
 
     public void configureCacheManager(AbstractResolver resolver) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParser.java
new file mode 100644
index 0000000..7762d60
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParser.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.externalresource.transport.http;
+
+import org.cyberneko.html.parsers.SAXParser;
+import org.gradle.api.internal.resource.ResourceException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ApacheDirectoryListingParser {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ApacheDirectoryListingParser.class);
+
+    public ApacheDirectoryListingParser() {
+    }
+
+    public List<URI> parse(URI baseURI, byte[] content, String contentType) throws Exception {
+        baseURI = addTrailingSlashes(baseURI);
+        if (contentType == null || !contentType.startsWith("text/html")) {
+            throw new ResourceException(String.format("Unsupported ContentType %s for DirectoryListing", contentType));
+        }
+        String contentEncoding = contentType.contains("charset=") ? contentType.substring(contentType.indexOf('=') + 1) : "utf-8";
+        final String htmlText = new String(content, contentEncoding);
+        final InputSource inputSource = new InputSource(new StringReader(htmlText));
+        final SAXParser htmlParser = new SAXParser();
+        final AnchorListerHandler anchorListerHandler = new AnchorListerHandler();
+        htmlParser.setContentHandler(anchorListerHandler);
+        htmlParser.parse(inputSource);
+
+        List<String> hrefs = anchorListerHandler.getHrefs();
+        List<URI> uris = resolveURIs(baseURI, hrefs);
+        return filterNonDirectChilds(baseURI, uris);
+    }
+
+    private URI addTrailingSlashes(URI uri) throws IOException, URISyntaxException {
+        if(uri.getPath() == null){
+            uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), "/", uri.getQuery(), uri.getFragment());
+        }else if (!uri.getPath().endsWith("/") && !uri.getPath().endsWith(".html")) {
+            uri = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath() + "/", uri.getQuery(), uri.getFragment());
+
+        }
+        return uri;
+    }
+
+    private List<URI> filterNonDirectChilds(URI baseURI, List<URI> inputURIs) throws MalformedURLException {
+        final int baseURIPort = baseURI.getPort();
+        final String baseURIHost = baseURI.getHost();
+        final String baseURIScheme = baseURI.getScheme();
+
+        List<URI> uris = new ArrayList<URI>();
+        final String prefixPath = baseURI.getPath();
+        for (URI parsedURI : inputURIs) {
+            if (parsedURI.getHost() != null && !parsedURI.getHost().equals(baseURIHost)) {
+                continue;
+            }
+            if (parsedURI.getScheme() != null && !parsedURI.getScheme().equals(baseURIScheme)) {
+                continue;
+            }
+            if (parsedURI.getPort() != baseURIPort) {
+                continue;
+            }
+            if (parsedURI.getPath() != null && !parsedURI.getPath().startsWith(prefixPath)) {
+                continue;
+            }
+            String childPathPart = parsedURI.getPath().substring(prefixPath.length(), parsedURI.getPath().length());
+            if (childPathPart.startsWith("../")) {
+                continue;
+            }
+            if (childPathPart.equals("") || childPathPart.split("/").length > 1) {
+                continue;
+            }
+
+            uris.add(parsedURI);
+        }
+        return uris;
+    }
+
+    private List<URI> resolveURIs(URI baseURI, List<String> hrefs) {
+        List<URI> uris = new ArrayList<URI>();
+        for (String href : hrefs) {
+            try {
+                uris.add(baseURI.resolve(href));
+            } catch (IllegalArgumentException ex) {
+                LOGGER.debug(String.format("Cannot resolve anchor: %s", href));
+            }
+        }
+        return uris;
+    }
+
+    private class AnchorListerHandler extends DefaultHandler {
+        List<String> hrefs = new ArrayList<String>();
+
+        public List<String> getHrefs() {
+            return hrefs;
+        }
+
+        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+            if (qName.equalsIgnoreCase("A")) {
+                final String href = atts.getValue("href");
+                if (href != null) {
+                    hrefs.add(href);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/CopyProgressListenerAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/CopyProgressListenerAdapter.java
new file mode 100644
index 0000000..de0d5fe
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/CopyProgressListenerAdapter.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.externalresource.transport.http;
+
+import org.apache.ivy.util.CopyProgressEvent;
+import org.apache.ivy.util.CopyProgressListener;
+
+public class CopyProgressListenerAdapter implements CopyProgressListener {
+    public void start(CopyProgressEvent evt) {
+    }
+
+    public void progress(CopyProgressEvent evt) {
+    }
+
+    public void end(CopyProgressEvent evt) {
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurer.java
index 12d254a..705a91f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurer.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurer.java
@@ -15,22 +15,26 @@
  */
 package org.gradle.api.internal.externalresource.transport.http;
 
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
 import org.apache.http.HttpRequest;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.AuthenticationException;
-import org.apache.http.auth.Credentials;
-import org.apache.http.auth.NTCredentials;
-import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.auth.*;
+import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
 import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.client.protocol.ClientContext;
 import org.apache.http.impl.auth.BasicScheme;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.ExecutionContext;
 import org.apache.http.protocol.HttpContext;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
 import org.gradle.api.internal.externalresource.transport.http.ntlm.NTLMCredentials;
 import org.gradle.api.internal.externalresource.transport.http.ntlm.NTLMSchemeFactory;
-import org.gradle.internal.UncheckedException;
 import org.gradle.util.GUtil;
 import org.gradle.util.GradleVersion;
 import org.slf4j.Logger;
@@ -43,18 +47,9 @@ public class HttpClientConfigurer {
     private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientConfigurer.class);
 
     private final HttpSettings httpSettings;
-    private final UsernamePasswordCredentials repositoryCredentials;
 
     public HttpClientConfigurer(HttpSettings httpSettings) {
         this.httpSettings = httpSettings;
-        repositoryCredentials = createRepositoryCredentials(httpSettings.getCredentials());
-    }
-
-    private UsernamePasswordCredentials createRepositoryCredentials(PasswordCredentials credentials) {
-        if (GUtil.isTrue(credentials.getUsername())) {
-            return new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword());
-        }
-        return null;
     }
 
     public void configure(DefaultHttpClient httpClient) {
@@ -62,11 +57,15 @@ public class HttpClientConfigurer {
         configureCredentials(httpClient, httpSettings.getCredentials());
         configureProxy(httpClient, httpSettings.getProxySettings());
         configureRetryHandler(httpClient);
+        configureUserAgent(httpClient);
     }
 
     private void configureCredentials(DefaultHttpClient httpClient, PasswordCredentials credentials) {
         if (GUtil.isTrue(credentials.getUsername())) {
             useCredentials(httpClient, credentials, AuthScope.ANY_HOST, AuthScope.ANY_PORT);
+
+            // Use preemptive authorisation if no other authorisation has been established
+            httpClient.addRequestInterceptor(new PreemptiveAuth(new BasicScheme()), 0);
         }
     }
 
@@ -88,7 +87,7 @@ public class HttpClientConfigurer {
         NTLMCredentials ntlmCredentials = new NTLMCredentials(credentials);
         Credentials ntCredentials = new NTCredentials(ntlmCredentials.getUsername(), ntlmCredentials.getPassword(), ntlmCredentials.getWorkstation(), ntlmCredentials.getDomain());
         httpClient.getCredentialsProvider().setCredentials(new AuthScope(host, port, AuthScope.ANY_REALM, AuthPolicy.NTLM), ntCredentials);
-        
+
         LOGGER.debug("Using {} and {} for authenticating against '{}:{}'", new Object[]{credentials, ntlmCredentials, host, port});
     }
 
@@ -100,15 +99,36 @@ public class HttpClientConfigurer {
         });
     }
 
-    public void configureMethod(HttpRequest method) {
-        method.addHeader("User-Agent", "Gradle/" + GradleVersion.current().getVersion());
+    public void configureUserAgent(DefaultHttpClient httpClient) {
+        String userAgent = "Gradle/" + GradleVersion.current().getVersion();
+        HttpProtocolParams.setUserAgent(httpClient.getParams(), userAgent);
+    }
+
+    static class PreemptiveAuth implements HttpRequestInterceptor {
+        private final AuthScheme authScheme;
+
+        PreemptiveAuth(AuthScheme authScheme) {
+            this.authScheme = authScheme;
+        }
+
+        public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
+
+            AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
+
+            if (authState.getAuthScheme() != null || authState.hasAuthOptions()) {
+                return;
+            }
 
-        // Do preemptive authentication for basic auth
-        if (repositoryCredentials != null) {
-            try {
-                method.addHeader(new BasicScheme().authenticate(repositoryCredentials, method));
-            } catch (AuthenticationException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
+            // If no authState has been established and this is a PUT or POST request, add preemptive authorisation
+            String requestMethod = request.getRequestLine().getMethod();
+            if (requestMethod.equals(HttpPut.METHOD_NAME) || requestMethod.equals(HttpPost.METHOD_NAME)) {
+                CredentialsProvider credentialsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
+                HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
+                Credentials credentials = credentialsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
+                if (credentials == null) {
+                    throw new HttpException("No credentials for preemptive authentication");
+                }
+                authState.update(authScheme, credentials);
             }
         }
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java
index eb81212..b02ca94 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientHelper.java
@@ -16,7 +16,6 @@
 
 package org.gradle.api.internal.externalresource.transport.http;
 
-import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpHead;
@@ -53,11 +52,7 @@ public class HttpClientHelper {
     }
     
     public HttpResponse performHead(String source) {
-        return performHead(source, false);
-    }
-    
-    public HttpResponse performHead(String source, boolean ignoreError) {
-        return processResponse(source, "HEAD", performRawHead(source), ignoreError);
+        return processResponse(source, "HEAD", performRawHead(source));
     }
 
     public HttpResponse performRawGet(String source) {
@@ -65,16 +60,7 @@ public class HttpClientHelper {
     }
 
     public HttpResponse performGet(String source) {
-        return performGet(source, false);
-    }
-    
-    public HttpResponse performGet(String source, boolean ignoreError) {
-        return processResponse(source, "GET", performRawGet(source), ignoreError);
-    }
-
-    public HttpRequest configureRequest(HttpRequest request) {
-        configurer.configureMethod(request);
-        return request;
+        return processResponse(source, "GET", performRawGet(source));
     }
 
     public HttpResponse performRequest(HttpRequestBase request) {
@@ -111,8 +97,6 @@ public class HttpClientHelper {
     }
 
     public HttpResponse performHttpRequest(HttpRequestBase request) throws IOException {
-        configureRequest(request);
-
         // Without this, HTTP Client prohibits multiple redirects to the same location within the same context
         httpContext.removeAttribute(DefaultRedirectStrategy.REDIRECT_LOCATIONS);
 
@@ -120,7 +104,7 @@ public class HttpClientHelper {
         return client.execute(request, httpContext);
     }
 
-    private HttpResponse processResponse(String source, String method, HttpResponse response, boolean ignoreError) {
+    private HttpResponse processResponse(String source, String method, HttpResponse response) {
         if (wasMissing(response)) {
             LOGGER.info("Resource missing. [HTTP {}: {}]", method, source);
             return null;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceLister.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceLister.java
index 24f590f..4e3d1fb 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceLister.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceLister.java
@@ -16,28 +16,74 @@
 
 package org.gradle.api.internal.externalresource.transport.http;
 
-import org.apache.ivy.util.url.ApacheURLLister;
+import org.gradle.api.internal.externalresource.ExternalResource;
 import org.gradle.api.internal.externalresource.transfer.ExternalResourceLister;
+import org.gradle.api.internal.resource.ResourceException;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
 
 public class HttpResourceLister implements ExternalResourceLister {
+    private HttpResourceAccessor accessor;
+
+    public HttpResourceLister(HttpResourceAccessor accessor) {
+        this.accessor = accessor;
+    }
 
     public List<String> list(String parent) throws IOException {
-        // Parse standard directory listing pages served up by Apache
-        ApacheURLLister urlLister = new ApacheURLLister();
-        List<URL> urls = urlLister.listAll(new URL(parent));
-        if (urls != null) {
-            List<String> ret = new ArrayList<String>(urls.size());
-            for (URL url : urls) {
-                ret.add(url.toExternalForm());
-            }
-            return ret;
+        URI baseURI;
+        try {
+            baseURI = new URI(parent);
+        } catch (URISyntaxException ex) {
+            throw new ResourceException(String.format("Unable to create URI from String '%s' ", parent), ex);
+        }
+        final ExternalResource resource = accessor.getResource(baseURI.toString());
+        if (resource == null) {
+            return null;
+        }
+        byte[] resourceContent = loadResourceContent(resource);
+        String contentType = getContentType(resource);
+        ApacheDirectoryListingParser directoryListingParser = new ApacheDirectoryListingParser();
+        try {
+            List<URI> uris = directoryListingParser.parse(baseURI, resourceContent, contentType);
+            return convertToStringList(uris);
+        } catch (Exception e) {
+            throw new ResourceException("Unable to parse Http directory listing", e);
+        }
+    }
+
+    private List<String> convertToStringList(List<URI> uris) {
+        List<String> ret = new ArrayList<String>(uris.size());
+        for (URI url : uris) {
+            ret.add(url.toString());
+        }
+        return ret;
+    }
+
+    private String getContentType(ExternalResource resource) {
+        if (resource instanceof HttpResponseResource) {
+            return ((HttpResponseResource) resource).getContentType();
         }
         return null;
     }
 
+    byte[] loadResourceContent(ExternalResource resource) throws IOException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            resource.writeTo(outputStream, new CopyProgressListenerAdapter());
+            return outputStream.toByteArray();
+        } finally {
+            try {
+                outputStream.close();
+            } finally {
+                resource.close();
+            }
+        }
+    }
+
+
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java
index 0489198..59c9d69 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResource.java
@@ -37,6 +37,7 @@ class HttpResponseResource extends AbstractExternalResource {
     private final String source;
     private final HttpResponse response;
     private final ExternalResourceMetaData metaData;
+    private boolean wasOpened;
 
     public HttpResponseResource(String method, String source, HttpResponse response) {
         this.method = method;
@@ -90,6 +91,11 @@ class HttpResponseResource extends AbstractExternalResource {
         }
     }
 
+    public String getContentType() {
+        final Header header = response.getFirstHeader(HttpHeaders.CONTENT_TYPE);
+        return header == null ? null : header.getValue();
+    }
+
     public boolean exists() {
         return true;
     }
@@ -99,7 +105,11 @@ class HttpResponseResource extends AbstractExternalResource {
     }
 
     public InputStream openStream() throws IOException {
+        if(wasOpened){
+            throw new IOException("Unable to open Stream as it was opened before.");
+        }
         LOGGER.debug("Attempting to download resource {}.", source);
+        this.wasOpened = true;
         return response.getEntity().getContent();
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java
index 432a430..1ed5c85 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/externalresource/transport/http/HttpTransport.java
@@ -37,8 +37,9 @@ public class HttpTransport implements RepositoryTransport {
 
     public ExternalResourceRepository getRepository() {
         HttpClientHelper http = new HttpClientHelper(new DefaultHttpSettings(credentials));
+        final HttpResourceAccessor accessor = new HttpResourceAccessor(http);
         return new DefaultExternalResourceRepository(
-                name, new HttpResourceAccessor(http), new HttpResourceUploader(http), new HttpResourceLister()
+                name, accessor, new HttpResourceUploader(http), new HttpResourceLister(accessor)
         );
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/UniquePathFileStore.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/UniquePathFileStore.java
index 13ea982..6ec2dd6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/UniquePathFileStore.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/filestore/UniquePathFileStore.java
@@ -71,7 +71,7 @@ public class UniquePathFileStore implements FileStore<String>, FileStoreSearcher
     private void saveIntoFileStore(File contentFile, File storageFile) {
         File parentDir = storageFile.getParentFile();
         if (!parentDir.mkdirs() && !parentDir.exists()) {
-            throw new GradleException(String.format("Unabled to create filestore directory %s", parentDir));
+            throw new GradleException(String.format("Unable to create filestore directory %s", parentDir));
         }
         if (!contentFile.renameTo(storageFile)) {
             throw new GradleException(String.format("Failed to copy downloaded content into storage file: %s", storageFile));
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java
index 37e8476..d8027e2 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.notations;
 
 import org.gradle.api.artifacts.ClientModule;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
 import org.gradle.api.internal.notations.api.NotationParser;
 import org.gradle.api.internal.notations.api.TopLevelNotationParser;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java
index e5af72b..d6945a6 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParser.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal.notations;
 import org.gradle.api.artifacts.SelfResolvingDependency;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.file.FileResolver;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java
index c8c5cf3..33dab35 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyFilesNotationParser.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.notations;
 
 import org.gradle.api.artifacts.SelfResolvingDependency;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
 import org.gradle.api.internal.notations.api.NotationParser;
 import org.gradle.api.internal.notations.parsers.TypedNotationParser;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java
index 56898db..c15e792 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyMapNotationParser.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.notations;
 
 import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
 import org.gradle.api.internal.notations.parsers.MapKey;
 import org.gradle.api.internal.notations.parsers.MapNotationParser;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
index 4c55545..9b1d318 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal.notations;
 
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
 import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
 import org.gradle.api.internal.notations.parsers.TypedNotationParser;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
index 3d601f4..cb258ce 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyStringNotationParser.java
@@ -19,7 +19,7 @@ package org.gradle.api.internal.notations;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.ClientModule;
 import org.gradle.api.artifacts.ExternalDependency;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ParsedModuleStringNotation;
 import org.gradle.api.internal.notations.parsers.TypedNotationParser;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
index 45d01d5..0c9b153 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.notations;
 
 import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
 import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/artifacts/ArtifactsTestUtils.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/artifacts/ArtifactsTestUtils.java
index 6a19a5e..e5bbc17 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/artifacts/ArtifactsTestUtils.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/artifacts/ArtifactsTestUtils.java
@@ -16,9 +16,9 @@
 package org.gradle.api.artifacts;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.api.internal.file.FileSource;
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
+import org.gradle.internal.Factory;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
 
@@ -41,9 +41,9 @@ public class ArtifactsTestUtils {
             allowing(artifactStub).getExtraAttribute(with(org.hamcrest.Matchers.notNullValue(String.class)));
             will(returnValue(null));
         }});
-        final FileSource artifactSource = context.mock(FileSource.class);
+        final Factory artifactSource = context.mock(Factory.class);
         context.checking(new Expectations() {{
-            allowing(artifactSource).get();
+            allowing(artifactSource).create();
             will(returnValue(file));
         }});
         final ResolvedDependency resolvedDependency = context.mock(ResolvedDependency.class);
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
index ed0698a..445acff 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal.artifacts
 import org.gradle.StartParameter
 import org.gradle.api.internal.ClassPathRegistry
 import org.gradle.api.internal.DomainObjectContext
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
 import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
@@ -34,7 +34,7 @@ import org.gradle.internal.service.ServiceRegistry
 import org.gradle.listener.ListenerManager
 import org.gradle.logging.LoggingManagerInternal
 import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.util.TimeProvider
+import org.gradle.internal.TimeProvider
 import spock.lang.Specification
 
 class DefaultDependencyManagementServicesTest extends Specification {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy
index 20e9204..f80783b 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.groovy
@@ -17,14 +17,13 @@ package org.gradle.api.internal.artifacts
 
 import org.apache.ivy.core.module.descriptor.Artifact
 import org.gradle.api.artifacts.ResolvedDependency
-import org.gradle.api.internal.file.FileSource
+import org.gradle.api.artifacts.ResolvedModuleVersion
+import org.gradle.internal.Factory
 import org.gradle.util.Matchers
 import spock.lang.Specification
 
-import org.gradle.api.artifacts.ResolvedModuleVersion
-
 class DefaultResolvedArtifactTest extends Specification {
-    final FileSource artifactSource = Mock()
+    final Factory artifactSource = Mock()
 
     def "uses extended attributes to determine classifier"() {
         Artifact ivyArtifact = Mock()
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
index 72846a9..4d50f9a 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.Task
 import org.gradle.api.artifacts.Module
 import org.gradle.api.artifacts.PublishArtifact
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java
deleted file mode 100644
index 8a527e7..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2007-2008 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 org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.publish.PublishEngine;
-import org.apache.ivy.core.publish.PublishOptions;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.util.WrapUtil;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultIvyDependencyPublisherTest {
-    JUnit4Mockery context = new JUnit4Mockery() {{
-            setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-    
-    private ModuleDescriptor moduleDescriptorDummy = context.mock(ModuleDescriptor.class);
-    private PublishOptionsFactory publishOptionsFactoryStub = context.mock(PublishOptionsFactory.class);
-    private PublishEngine publishEngineMock = context.mock(PublishEngine.class);
-    private List<DependencyResolver> expectedResolverList = WrapUtil.toList(context.mock(DependencyResolver.class));
-    private DefaultIvyDependencyPublisher ivyDependencyPublisher = new DefaultIvyDependencyPublisher(publishOptionsFactoryStub);
-    private String expectedConf = "conf1";
-    private PublishOptions expectedPublishOptions = new PublishOptions();
-    private File someDescriptorDestination = new File("somePath");
-
-    @Test
-    public void testPublishWithUploadModuleDescriptorFalse() throws IOException {
-        context.checking(new Expectations() {{
-                allowing(publishOptionsFactoryStub).createPublishOptions(WrapUtil.toSet(expectedConf), someDescriptorDestination);
-                will(returnValue(expectedPublishOptions));
-
-                one(publishEngineMock).publish(
-                        moduleDescriptorDummy,
-                        DefaultIvyDependencyPublisher.ARTIFACT_PATTERN,
-                        expectedResolverList.get(0),
-                        expectedPublishOptions);
-        }});
-
-        ivyDependencyPublisher.publish(WrapUtil.toSet(expectedConf), expectedResolverList, moduleDescriptorDummy, someDescriptorDestination, publishEngineMock);
-    }
-
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java
deleted file mode 100644
index 5563d7a..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2007-2009 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 org.gradle.api.internal.artifacts.ivyservice;
-
-import org.apache.ivy.core.publish.PublishOptions;
-import org.gradle.util.WrapUtil;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultPublishOptionsFactoryTest {
-    private DefaultPublishOptionsFactory publishOptionsFactory;
-    private static final String TEST_CONF = "conf1";
-
-    @Before
-    public void setUp() {
-        publishOptionsFactory = new DefaultPublishOptionsFactory();
-    }
-
-    @Test
-    public void testCreatePublishOptionsWithUploadModuleDescriptorTrue() {
-        File someDescriptorDestination = new File("somePath");
-        PublishOptions publishOptions = publishOptionsFactory.createPublishOptions(WrapUtil.toSet(TEST_CONF), someDescriptorDestination);
-        assertThat(publishOptions.getSrcIvyPattern(), equalTo(someDescriptorDestination.getAbsolutePath()));
-        checkCommonValues(publishOptions);
-    }
-
-    @Test
-    public void testCreatePublishOptionsWithUploadModuleDescriptorFalse() {
-        PublishOptions publishOptions = publishOptionsFactory.createPublishOptions(WrapUtil.toSet(TEST_CONF), null);
-        assertThat(publishOptions.getSrcIvyPattern(), equalTo(null));
-        checkCommonValues(publishOptions);
-    }
-
-    private void checkCommonValues(PublishOptions publishOptions) {
-        assertThat(publishOptions.getConfs(), equalTo(WrapUtil.toArray(TEST_CONF)));
-        assertThat(publishOptions.isOverwrite(), equalTo(true));
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy
new file mode 100644
index 0000000..1558c93
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.artifacts.ivyservice
+
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 5/11/12
+ */
+class DefaultUnresolvedDependencySpec extends Specification {
+
+    def "provides module details"() {
+        when:
+        def dep = new DefaultUnresolvedDependency(ModuleRevisionId.newInstance('org.foo', "foo", '1.0'), new RuntimeException("boo!"))
+
+        then:
+        dep.selector.group == 'org.foo'
+        dep.selector.name == 'foo'
+        dep.selector.version == '1.0'
+        dep.id == 'org.foo#foo;1.0'
+        dep.toString() == 'org.foo:foo:1.0'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
index a0c808a..46f0907 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
@@ -16,8 +16,8 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.Ivy;
+import org.apache.ivy.core.event.EventManager;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.publish.PublishEngine;
 import org.apache.ivy.core.settings.IvySettings;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.artifacts.Configuration;
@@ -25,6 +25,7 @@ import org.gradle.api.artifacts.Module;
 import org.gradle.api.internal.artifacts.configurations.*;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.equalTo;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
@@ -37,8 +38,6 @@ import java.text.ParseException;
 import java.util.List;
 import java.util.Set;
 
-import static org.hamcrest.Matchers.equalTo;
-
 /**
  * @author Hans Dockter
  */
@@ -48,7 +47,6 @@ public class IvyBackedArtifactPublisherTest {
 
     private ModuleDescriptor publishModuleDescriptorDummy = context.mock(ModuleDescriptor.class);
     private ModuleDescriptor fileModuleDescriptorMock = context.mock(ModuleDescriptor.class);
-    private PublishEngine publishEngineDummy = context.mock(PublishEngine.class);
     private DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider.class);
     private ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
     private IvyFactory ivyFactoryStub = context.mock(IvyFactory.class);
@@ -60,6 +58,7 @@ public class IvyBackedArtifactPublisherTest {
     @Test
     public void testPublish() throws IOException, ParseException {
         final IvySettings ivySettingsDummy = new IvySettings();
+        final EventManager ivyEventManagerDummy = new EventManager();
         final ConfigurationInternal configuration = context.mock(ConfigurationInternal.class);
         final Set<Configuration> configurations = createConfiguration();
         final File someDescriptorDestination = new File("somePath");
@@ -67,8 +66,8 @@ public class IvyBackedArtifactPublisherTest {
         final Module moduleDummy = context.mock(Module.class, "moduleForResolve");
         final IvyBackedArtifactPublisher ivyService = createIvyService();
 
-        setUpForPublish(configurations, publishResolversDummy, moduleDummy,
-                ivySettingsDummy);
+        setUpIvyFactory(ivySettingsDummy, ivyEventManagerDummy);
+        setUpForPublish(configurations, publishResolversDummy, moduleDummy, ivySettingsDummy);
 
         final Set<String> expectedConfigurations = Configurations.getNames(configurations, true);
         context.checking(new Expectations() {{
@@ -82,7 +81,7 @@ public class IvyBackedArtifactPublisherTest {
             will(returnValue(new DefaultResolutionStrategy()));
             one(fileModuleDescriptorMock).toIvyFile(someDescriptorDestination);
             one(ivyDependencyPublisherMock).publish(expectedConfigurations,
-                    publishResolversDummy, publishModuleDescriptorDummy, someDescriptorDestination, publishEngineDummy);
+                    publishResolversDummy, publishModuleDescriptorDummy, someDescriptorDestination, ivyEventManagerDummy);
         }});
 
         ivyService.publish(configuration, someDescriptorDestination);
@@ -133,13 +132,9 @@ public class IvyBackedArtifactPublisherTest {
             allowing(dependencyMetaDataProviderMock).getModule();
             will(returnValue(moduleDummy));
 
-            allowing(settingsConverterStub).convertForPublish(publishResolversDummy
-            );
+            allowing(settingsConverterStub).convertForPublish(publishResolversDummy);
             will(returnValue(ivySettingsDummy));
 
-            allowing(setUpIvyFactory(ivySettingsDummy)).getPublishEngine();
-            will(returnValue(publishEngineDummy));
-
             allowing(publishModuleDescriptorConverter).convert(with(equalTo(configurations)),
                     with(equalTo(moduleDummy)));
             will(returnValue(publishModuleDescriptorDummy));
@@ -150,7 +145,7 @@ public class IvyBackedArtifactPublisherTest {
         }});
     }
 
-    private Ivy setUpIvyFactory(final IvySettings ivySettingsDummy) {
+    private Ivy setUpIvyFactory(final IvySettings ivySettingsDummy, final EventManager ivyEventManagerDummy) {
         final Ivy ivyStub = context.mock(Ivy.class);
         context.checking(new Expectations() {{
             allowing(ivyFactoryStub).createIvy(ivySettingsDummy);
@@ -158,6 +153,9 @@ public class IvyBackedArtifactPublisherTest {
 
             allowing(ivyStub).getSettings();
             will(returnValue(ivySettingsDummy));
+
+            allowing(ivyStub).getEventManager();
+            will(returnValue(ivyEventManagerDummy));
         }});
         return ivyStub;
     }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy
index 17adb69..f92cce2 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepositoryTest.groovy
@@ -32,7 +32,7 @@ import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
 import org.gradle.api.internal.externalresource.metadata.ExternalResourceMetaData
 import org.gradle.api.internal.externalresource.metadata.DefaultExternalResourceMetaData
-import org.gradle.util.TrueTimeProvider
+import org.gradle.internal.TrueTimeProvider
 
 class CachingModuleVersionRepositoryTest extends Specification {
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy
index ad10db6..34f5a64 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-
 import org.apache.ivy.plugins.resolver.DependencyResolver
 import spock.lang.Specification
 import org.apache.ivy.plugins.resolver.AbstractPatternsBasedResolver
@@ -24,11 +23,11 @@ import org.gradle.api.internal.artifacts.repositories.ExternalResourceResolver
 
 public class DependencyResolverIdentifierTest extends Specification {
     def "uses dependency resolver name"() {
-        when:
+        given:
         DependencyResolver resolver = Mock()
         resolver.name >> "resolver-name"
 
-        then:
+        expect:
         new DependencyResolverIdentifier(resolver).name == "resolver-name"
     }
 
@@ -38,12 +37,11 @@ public class DependencyResolverIdentifierTest extends Specification {
         DependencyResolver resolver1a = Mock()
         DependencyResolver resolver2 = Mock()
 
-        when:
         resolver1.name >> 'name1'
         resolver1a.name >> 'name1'
         resolver2.name >> 'name2'
 
-        then:
+        expect:
         id(resolver1) == id(resolver1a)
         id(resolver1) != id(resolver2)
     }
@@ -55,7 +53,6 @@ public class DependencyResolverIdentifierTest extends Specification {
         AbstractPatternsBasedResolver resolver2 = Mock()
         AbstractPatternsBasedResolver resolver2a = Mock()
 
-        when:
         resolver1.ivyPatterns >> ['ivy1', 'ivy2']
         resolver1.artifactPatterns >> ['artifact1', 'artifact2']
         resolver1a.ivyPatterns >> ['ivy1', 'ivy2']
@@ -65,7 +62,7 @@ public class DependencyResolverIdentifierTest extends Specification {
         resolver2a.ivyPatterns >> ['ivy1', 'ivy2']
         resolver2a.artifactPatterns >> ['artifact1', 'different']
 
-        then:
+        expect:
         id(resolver1) == id(resolver1a)
         id(resolver1) != id(resolver2)
         id(resolver1) != id(resolver2a)
@@ -79,7 +76,6 @@ public class DependencyResolverIdentifierTest extends Specification {
         ExternalResourceResolver resolver2 = Mock()
         ExternalResourceResolver resolver2a = Mock()
 
-        when:
         resolver1.ivyPatterns >> ['ivy1', 'ivy2']
         resolver1.artifactPatterns >> ['artifact1', 'artifact2']
         resolver1a.ivyPatterns >> ['ivy1', 'ivy2']
@@ -89,7 +85,7 @@ public class DependencyResolverIdentifierTest extends Specification {
         resolver2a.ivyPatterns >> ['ivy1', 'ivy2']
         resolver2a.artifactPatterns >> ['artifact1', 'different']
 
-        then:
+        expect:
         id(resolver1) == id(resolver1a)
         id(resolver1) != id(resolver2)
         id(resolver1) != id(resolver2a)
@@ -101,7 +97,6 @@ public class DependencyResolverIdentifierTest extends Specification {
         AbstractPatternsBasedResolver resolver1 = Mock()
         AbstractPatternsBasedResolver resolver2 = Mock()
 
-        when:
         resolver1.ivyPatterns >> ['ivy1']
         resolver1.artifactPatterns >> ['artifact1']
         resolver1.m2compatible >> false
@@ -109,7 +104,7 @@ public class DependencyResolverIdentifierTest extends Specification {
         resolver2.artifactPatterns >> ['artifact1']
         resolver2.m2compatible >> true
 
-        then:
+        expect:
         id(resolver1) != id(resolver2)
     }
 
@@ -118,14 +113,13 @@ public class DependencyResolverIdentifierTest extends Specification {
         ExternalResourceResolver resolver1 = Mock()
         ExternalResourceResolver resolver2 = Mock()
 
-        when:
         resolver1.ivyPatterns >> ['ivy1']
         resolver1.artifactPatterns >> ['artifact1']
         resolver2.ivyPatterns >> ['ivy1']
         resolver2.artifactPatterns >> ['artifact1']
         resolver2.m2compatible >> true
 
-        then:
+        expect:
         id(resolver1) != id(resolver2)
     }
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy
new file mode 100644
index 0000000..ba2682c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DownloadedIvyModuleDescriptorParserTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import spock.lang.Specification
+import org.apache.ivy.plugins.parser.ParserSettings
+import org.apache.ivy.plugins.repository.Resource
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+import org.apache.ivy.core.settings.IvySettings
+
+class DownloadedIvyModuleDescriptorParserTest extends Specification {
+    @Rule TemporaryFolder tmpDir
+    final DownloadedIvyModuleDescriptorParser parser = new DownloadedIvyModuleDescriptorParser()
+
+    def "discards the default attribute"() {
+        def ivyFile = tmpDir.createFile("ivy.xml")
+        ivyFile.text = """<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+    <info organisation="org" module="someModule" revision="1.2" default="true"/>
+</ivy-module>
+"""
+        def url = ivyFile.toURI().toURL()
+        ParserSettings settings = new IvySettings()
+        Resource resource = Mock()
+
+        when:
+        def descriptor = parser.parseDescriptor(settings, url, resource, true)
+
+        then:
+        !descriptor.default
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
new file mode 100644
index 0000000..ff07174
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParserTest.groovy
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2011 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 org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.apache.ivy.core.module.id.ArtifactRevisionId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.plugins.parser.ParserSettings
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Issue
+
+class GradlePomModuleDescriptorParserTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    final GradlePomModuleDescriptorParser parser = new GradlePomModuleDescriptorParser()
+    final ParserSettings ivySettings = Mock()
+    TestFile pomFile
+
+    def "setup"() {
+        pomFile = tmpDir.file('foo')
+    }
+
+    def "parses simple pom"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <name>Test Artifact One</name>
+    <description>The first test artifact</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+    }
+
+    def "pom with dependency with classifier"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+            <classifier>classifier-two</classifier>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDependencyArtifact(descriptor.dependencies.first(), 'artifact-two', 'jar', 'jar', 'classifier-two')
+    }
+
+    @Issue("GRADLE-2068")
+    def "pom with dependency with empty classifier is treated like dependency without classifier"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>group-two</groupId>
+            <artifactId>artifact-two</artifactId>
+            <version>version-two</version>
+            <classifier></classifier>
+        </dependency>
+    </dependencies>
+</project>
+"""
+        and:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
+        descriptor.dependencies.length == 1
+        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
+        hasDefaultDependencyArtifact(descriptor.dependencies.first())
+    }
+
+    @Issue("GRADLE-2076")
+    def "pom with packaging of type eclipse-plugin creates jar artifact"() {
+        when:
+        pomFile << """
+<project>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>group-one</groupId>
+    <artifactId>artifact-one</artifactId>
+    <version>version-one</version>
+    <packaging>eclipse-plugin</packaging>
+</project>
+"""
+        and:
+        def descriptor = parsePom()
+
+        then:
+        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
+        hasArtifact(descriptor, 'artifact-one', 'eclipse-plugin', 'jar')
+        descriptor.dependencies.length == 0
+    }
+
+    private ModuleDescriptor parsePom() {
+        parser.parseDescriptor(ivySettings, pomFile.toURI().toURL(), false)
+    }
+
+    private void hasArtifact(ModuleDescriptor descriptor, String name, String type, String ext, String classifier = null) {
+        descriptor.allArtifacts.length == 1
+        def artifact = descriptor.allArtifacts.first()
+        assert artifact.id == artifactId(descriptor.moduleRevisionId, name, type, ext)
+        assert artifact.extraAttributes['classifier'] == classifier
+    }
+    
+    private void hasDefaultDependencyArtifact(DependencyDescriptor descriptor) {
+        descriptor.allDependencyArtifacts.length == 0
+    }
+
+    private void hasDependencyArtifact(DependencyDescriptor descriptor, String name, String type, String ext, String classifier = null) {
+        descriptor.allDependencyArtifacts.length == 1
+        def artifact = descriptor.allDependencyArtifacts.first()
+        assert artifact.name == name
+        assert artifact.type == type
+        assert artifact.ext == ext
+        assert artifact.extraAttributes['classifier'] == classifier
+    }
+
+    private ModuleRevisionId moduleId(String group, String name, String version) {
+        ModuleRevisionId.newInstance(group, name, version)
+    }
+
+    private ArtifactRevisionId artifactId(ModuleRevisionId moduleId, String name, String type, String ext) {
+        ArtifactRevisionId.newInstance(moduleId, name, type, ext)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParserTest.groovy
deleted file mode 100644
index 2cce972..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/PomParserTest.groovy
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
-
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor
-import org.apache.ivy.core.module.id.ArtifactRevisionId
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.apache.ivy.plugins.parser.ParserSettings
-import org.gradle.util.TemporaryFolder
-import org.gradle.util.TestFile
-import org.junit.Rule
-import spock.lang.Specification
-import spock.lang.Issue
-
-class PomParserTest extends Specification {
-    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
-    
-    final ParserSettings ivySettings = Mock()
-    TestFile pomFile
-
-    def "setup"() {
-        pomFile = tmpDir.file('foo')
-    }
-
-    def "parses simple pom"() {
-        when:
-        pomFile << """
-<project>
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>group-one</groupId>
-    <artifactId>artifact-one</artifactId>
-    <version>version-one</version>
-    <name>Test Artifact One</name>
-    <description>The first test artifact</description>
-
-    <dependencies>
-        <dependency>
-            <groupId>group-two</groupId>
-            <artifactId>artifact-two</artifactId>
-            <version>version-two</version>
-        </dependency>
-    </dependencies>
-</project>
-"""
-        and:
-        def descriptor = parsePom()
-
-        then:
-        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
-        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
-        descriptor.dependencies.length == 1
-        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
-        hasDefaultDependencyArtifact(descriptor.dependencies.first())
-    }
-
-    def "pom with dependency with classifier"() {
-        when:
-        pomFile << """
-<project>
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>group-one</groupId>
-    <artifactId>artifact-one</artifactId>
-    <version>version-one</version>
-
-    <dependencies>
-        <dependency>
-            <groupId>group-two</groupId>
-            <artifactId>artifact-two</artifactId>
-            <version>version-two</version>
-            <classifier>classifier-two</classifier>
-        </dependency>
-    </dependencies>
-</project>
-"""
-        and:
-        def descriptor = parsePom()
-
-        then:
-        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
-        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
-        descriptor.dependencies.length == 1
-        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
-        hasDependencyArtifact(descriptor.dependencies.first(), 'artifact-two', 'jar', 'jar', 'classifier-two')
-    }
-
-    @Issue("GRADLE-2068")
-    def "pom with dependency with empty classifier is treated like dependency without classifier"() {
-        when:
-        pomFile << """
-<project>
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>group-one</groupId>
-    <artifactId>artifact-one</artifactId>
-    <version>version-one</version>
-
-    <dependencies>
-        <dependency>
-            <groupId>group-two</groupId>
-            <artifactId>artifact-two</artifactId>
-            <version>version-two</version>
-            <classifier></classifier>
-        </dependency>
-    </dependencies>
-</project>
-"""
-        and:
-        def descriptor = parsePom()
-
-        then:
-        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
-        hasArtifact(descriptor, 'artifact-one', 'jar', 'jar')
-        descriptor.dependencies.length == 1
-        descriptor.dependencies.first().dependencyRevisionId == moduleId('group-two', 'artifact-two', 'version-two')
-        hasDefaultDependencyArtifact(descriptor.dependencies.first())
-    }
-
-    @Issue("GRADLE-2076")
-    def "pom with packaging of type eclipse-plugin creates jar artifact"() {
-        when:
-        pomFile << """
-<project>
-    <modelVersion>4.0.0</modelVersion>
-    <groupId>group-one</groupId>
-    <artifactId>artifact-one</artifactId>
-    <version>version-one</version>
-    <packaging>eclipse-plugin</packaging>
-</project>
-"""
-        and:
-        def descriptor = parsePom()
-
-        then:
-        descriptor.moduleRevisionId == moduleId('group-one', 'artifact-one', 'version-one')
-        hasArtifact(descriptor, 'artifact-one', 'eclipse-plugin', 'jar')
-        descriptor.dependencies.length == 0
-    }
-
-    private ModuleDescriptor parsePom() {
-        GradlePomModuleDescriptorParser.getInstance().parseDescriptor(ivySettings, pomFile.toURI().toURL(), false)
-    }
-
-    private void hasArtifact(ModuleDescriptor descriptor, String name, String type, String ext, String classifier = null) {
-        descriptor.allArtifacts.length == 1
-        def artifact = descriptor.allArtifacts.first()
-        assert artifact.id == artifactId(descriptor.moduleRevisionId, name, type, ext)
-        assert artifact.extraAttributes['classifier'] == classifier
-    }
-    
-    private void hasDefaultDependencyArtifact(DependencyDescriptor descriptor) {
-        descriptor.allDependencyArtifacts.length == 0
-    }
-
-    private void hasDependencyArtifact(DependencyDescriptor descriptor, String name, String type, String ext, String classifier = null) {
-        descriptor.allDependencyArtifacts.length == 1
-        def artifact = descriptor.allDependencyArtifacts.first()
-        assert artifact.name == name
-        assert artifact.type == type
-        assert artifact.ext == ext
-        assert artifact.extraAttributes['classifier'] == classifier
-    }
-
-    private ModuleRevisionId moduleId(String group, String name, String version) {
-        ModuleRevisionId.newInstance(group, name, version)
-    }
-
-    private ArtifactRevisionId artifactId(ModuleRevisionId moduleId, String name, String type, String ext) {
-        ArtifactRevisionId.newInstance(moduleId, name, type, ext)
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
index d97f70f..8e973ca 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
@@ -86,7 +86,7 @@ public class DefaultArtifactsToModuleDescriptorConverterTest {
     @Test
     public void testResolveStrategy() {
         PublishArtifact publishArtifact = createNamedPublishArtifact("someName");
-        Map<String, String> expectedExtraAttributes = WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
+        Map<String, String> expectedExtraAttributes = WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_ABSOLUTE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
         assertThat(
                 DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY.createExtraAttributes(publishArtifact),
                 equalTo(expectedExtraAttributes));
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
index 161661e..d3d6f06 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
@@ -23,7 +23,6 @@ import org.apache.ivy.core.resolve.ResolveEngine
 import org.apache.ivy.core.resolve.ResolveOptions
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher
 import org.apache.ivy.plugins.matcher.PatternMatcher
-import org.apache.ivy.plugins.version.VersionMatcher
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.DefaultResolvedArtifact
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
@@ -42,7 +41,6 @@ class DependencyGraphBuilderTest extends Specification {
     final ResolveData resolveData = new ResolveData(resolveEngine, new ResolveOptions())
     final ModuleConflictResolver conflictResolver = Mock()
     final DependencyToModuleVersionIdResolver dependencyResolver = Mock()
-    final VersionMatcher versionMatcher = Mock()
     final DefaultModuleDescriptor root = revision('root')
     final DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, dependencyResolver, conflictResolver)
 
@@ -509,7 +507,7 @@ class DependencyGraphBuilderTest extends Specification {
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.id == 'group#c;1.0'
+        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -539,7 +537,7 @@ class DependencyGraphBuilderTest extends Specification {
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.id == 'group#unknown;1.0'
+        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'unknown', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -568,7 +566,7 @@ class DependencyGraphBuilderTest extends Specification {
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.id == 'group#c;1.0'
+        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -598,7 +596,7 @@ class DependencyGraphBuilderTest extends Specification {
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.id == 'group#c;1.0'
+        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionNotFoundException
 
         when:
@@ -628,7 +626,7 @@ class DependencyGraphBuilderTest extends Specification {
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.id == 'group#c;1.0'
+        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
@@ -657,7 +655,7 @@ class DependencyGraphBuilderTest extends Specification {
         then:
         result.unresolvedModuleDependencies.size() == 1
         def unresolved = result.unresolvedModuleDependencies.iterator().next()
-        unresolved.id == 'group#c;1.0'
+        unresolved.selector == new DefaultModuleVersionIdentifier('group', 'c', '1.0')
         unresolved.problem instanceof ModuleVersionResolveException
 
         when:
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy
index 1ef8549..67608f3 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/mvnsettings/DefaultLocalMavenRepositoryLocatorTest.groovy
@@ -19,6 +19,8 @@ import org.gradle.util.TemporaryFolder
 
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.util.TestFile
+import spock.lang.Unroll
 
 class DefaultLocalMavenRepositoryLocatorTest extends Specification {
     @Rule TemporaryFolder tmpDir = new TemporaryFolder()
@@ -43,6 +45,30 @@ class DefaultLocalMavenRepositoryLocatorTest extends Specification {
         locator.localMavenRepository == new File("${System.getProperty("user.home")}/.m2/repository")
     }
 
+    def "throws exception on broken global settings file with decent error message"() {
+        given:
+        def settingsFile = locations.globalSettingsFile
+        settingsFile << "broken content"
+        when:
+        locator.localMavenRepository
+        then:
+        def ex = thrown(CannotLocateLocalMavenRepositoryException);
+        ex.message == "Unable to parse local maven settings"
+        ex.cause.message.contains(settingsFile.absolutePath)
+    }
+
+    def "throws exception on broken user settings file with decent error message"() {
+        given:
+        def settingsFile = locations.userSettingsFile
+        settingsFile << "broken content"
+        when:
+        locator.localMavenRepository
+        then:
+        def ex = thrown(CannotLocateLocalMavenRepositoryException)
+        ex.message == "Unable to parse local maven settings"
+        ex.cause.message.contains(settingsFile.absolutePath)
+    }
+
     def "honors location specified in user settings file"() {
         writeSettingsFile(locations.userSettingsFile, repo1)
 
@@ -85,6 +111,23 @@ class DefaultLocalMavenRepositoryLocatorTest extends Specification {
         locator.localMavenRepository == tmpDir.file("sys/prop/value/env/var/value")
     }
 
+    @Unroll
+    def "unresolvable placeholder for #propType throws exception with decent error message"() {
+        TestFile repoPath = tmpDir.file("\${$prop}")
+        writeSettingsFile(locations.userSettingsFile, repoPath)
+        when:
+        locator.localMavenRepository
+        then:
+        def ex = thrown(CannotLocateLocalMavenRepositoryException);
+        ex.message == "Cannot resolve placeholder '${prop}' in value '${repoPath.absolutePath}'"
+        where:
+        prop                  |   propType
+        'sys.unknown.prop'    |   "system property"
+        'env.unknown.ENV_VAR' |   "environment variable"
+    }
+
+
+
     private void writeSettingsFile(File settings, File repo) {
         writeSettingsFile(settings, repo.absolutePath)
     }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
index 4fbcc72..417b959 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
@@ -19,7 +19,6 @@ import org.apache.ivy.core.cache.RepositoryCacheManager
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.repositories.PasswordCredentials
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
-import org.gradle.api.internal.artifacts.repositories.transport.file.FileExternalResourceRepository
 import org.gradle.api.internal.externalresource.transport.file.FileTransport
 import org.gradle.api.internal.externalresource.transport.http.HttpTransport
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
@@ -109,7 +108,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
 
         then:
         resolver instanceof ExternalResourceResolver
-        resolver.repository instanceof FileExternalResourceRepository
+        resolver.repository instanceof ExternalResourceRepository
         resolver.name == 'name'
         resolver.artifactPatterns == ["${file.absolutePath}/[organisation]/[artifact]-[revision].[ext]", "${file.absolutePath}/[organisation]/[module]/[artifact]-[revision].[ext]"] as List
         resolver.ivyPatterns == ["${file.absolutePath}/[organisation]/[module]/ivy-[revision].xml"] as List
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy
index 7e4cb92..231ff72 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultResolverFactoryTest.groovy
@@ -20,7 +20,7 @@ import org.apache.ivy.plugins.resolver.DependencyResolver
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.dsl.RepositoryHandler
 import org.gradle.api.artifacts.repositories.ArtifactRepository
-import org.gradle.api.internal.DirectInstantiator
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
 import org.gradle.api.internal.file.FileResolver
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
index a0fa209..0fa2ad5 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/resolutioncache/DefaultArtifactResolutionCacheTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.cache.internal.CacheFactory
 import org.gradle.cache.internal.DefaultCacheRepository
 import org.gradle.testfixtures.internal.InMemoryCacheFactory
 import org.gradle.util.TemporaryFolder
-import org.gradle.util.TimeProvider
+import org.gradle.internal.TimeProvider
 import org.junit.Rule
 import spock.lang.Specification
 import spock.lang.Unroll
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParserTest.groovy
new file mode 100644
index 0000000..2b8b942
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ApacheDirectoryListingParserTest.groovy
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.externalresource.transport.http
+
+import org.gradle.api.internal.resource.ResourceException;
+import org.gradle.util.Resources
+import org.junit.Rule
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.junit.Assert.assertNotNull
+
+class ApacheDirectoryListingParserTest extends Specification {
+    @Rule public final Resources resources = new Resources();
+
+    private static final CONTENT_TYPE= "text/html;charset=utf-8";
+    private URI baseUrl = URI.create("http://testrepo/")
+    private ApacheDirectoryListingParser parser = new ApacheDirectoryListingParser();
+
+    def "parse returns empty List if no link can be found"() {
+        expect:
+        List urls = parser.parse(baseUrl, "<html>no link here</html>".bytes, CONTENT_TYPE)
+        assertNotNull(urls)
+        urls.isEmpty()
+    }
+
+    def "addTrailingSlashes adds trailing slashes on relative URL if not exist"() {
+            expect:
+            new URI(resultingURI) == parser.addTrailingSlashes(new URI(inputURI))
+            where:
+            inputURI                        | resultingURI
+            "http://testrepo"               | "http://testrepo/"
+            "http://testrepo/"              | "http://testrepo/"
+            "http://testrepo/index.html"    | "http://testrepo/index.html"
+        }
+
+    def "parse handles multiple listed links"() {
+        def html = """
+        <a href="directory1">directory1</a>
+        <a href="directory2">directory2</a>
+        <a href="directory3">directory3</a>
+        <a href="directory4">directory4</a>"""
+        expect:
+        def uris = parser.parse(baseUrl, html.bytes, CONTENT_TYPE)
+        assertNotNull(uris)
+        uris.collect {it.toString()} == ["http://testrepo/directory1", "http://testrepo/directory2", "http://testrepo/directory3", "http://testrepo/directory4"]
+    }
+
+    def "only text/html content type is supported"() {
+        def html = """
+        <a href="directory1">directory1</a>
+        <a href="directory2">directory2</a>"""
+        when:
+        parser.parse(baseUrl, html.bytes, contentType)
+        then:
+        thrown(ResourceException)
+        where:
+        contentType << ["text/plain", "application/octetstream"]
+    }
+
+    @Unroll
+    def "parse ignores #descr"() {
+        expect:
+        parser.parse(baseUrl, "<a href=\"${href}\">link</a>".toString().bytes, CONTENT_TYPE).isEmpty()
+        where:
+        href                                                | descr
+        "http://anothertestrepo/"                           | "URLs which aren't children of base URL"
+        "../"                                               | "links to parent URLs of base URL"
+        "http://[2h:23:3]"                                  | "invalid URLs"
+        "dir1/subdir1"                                      | "links to nested subdirectories"
+        "<![CDATA[<a href=\"directory2\">directory2</a>]]>" | "links in CDATA blocks"
+        "#achor"                                            | "anchor links"
+        "<a name=\"anchorname\">headline</a>"               | "anchor definitions"
+    }
+
+
+    @Unroll
+    def "parseLink handles #urlDescr"() {
+        def listingParser = new ApacheDirectoryListingParser()
+        expect:
+        def foundURIs = listingParser.parse(URI.create(baseUri), "<a href=\"${href}\">link</a>".toString().bytes, CONTENT_TYPE)
+        !foundURIs.isEmpty()
+        foundURIs.collect {it.toString()} == ["${baseUri}/directory1"]
+        where:
+        baseUri                 | href                        | urlDescr
+        "http://testrepo"       |  "directory1"               | "relative URLS"
+        "http://testrepo"       |"/directory1"                | "absolute URLS"
+        "http://testrepo"       |"./directory1"               | "explicit relative URLS"
+        "http://testrepo"       |"http://testrepo/directory1" | "complete URLS"
+        "http://testrepo"       | "http://testrepo/directory1"| "hrefs with truncated text"
+        "http://testrepo"       | "http://testrepo/directory1"| "hrefs with truncated text"
+        "http://[2001:db8::7]"  |"directory1"                 | "ipv6 host with relative URLS"
+        "http://[2001:db8::7]"  |"./directory1"               | "ipv6 host with explicit relative URLS"
+        "http://192.0.0.10"     |"directory1"                 | "ipv4 host with relative URLS"
+        "http://192.0.0.10"     |"./directory1"               | "ipv4 host with relative URLS"
+    }
+
+    @Unroll
+    def "parse is compatible with #repoType"() {
+        setup:
+        def byte[] content =  resources.getResource("${repoType}_dirlisting.html").bytes
+        expect:
+        List<URI> urls = new ApacheDirectoryListingParser().parse(new URI(artifactRootURI), content, CONTENT_TYPE)
+        urls.collect {it.toString()} as Set == ["${artifactRootURI}3.7/",
+                "${artifactRootURI}3.8/",
+                "${artifactRootURI}3.8.1/",
+                "${artifactRootURI}3.8.2/",
+                "${artifactRootURI}4.0/",
+                "${artifactRootURI}4.1/",
+                "${artifactRootURI}4.10/",
+                "${artifactRootURI}4.2/",
+                "${artifactRootURI}4.3/",
+                "${artifactRootURI}4.3.1/",
+                "${artifactRootURI}4.4/",
+                "${artifactRootURI}4.5/",
+                "${artifactRootURI}4.6/",
+                "${artifactRootURI}4.7/",
+                "${artifactRootURI}4.8/",
+                "${artifactRootURI}4.8.1/",
+                "${artifactRootURI}4.8.2/",
+                "${artifactRootURI}4.9/",
+                "${artifactRootURI}maven-metadata.xml",
+                "${artifactRootURI}maven-metadata.xml.md5",
+                "${artifactRootURI}maven-metadata.xml.sha1"] as Set
+        where:
+        artifactRootURI                                                         | repoType
+        "http://localhost:8081/artifactory/repo1/junit/junit/"                  | "artifactory"
+        "http://repo1.maven.org/maven2/junit/junit/"                            | "mavencentral"
+        "http://localhost:8081/nexus/content/repositories/central/junit/junit/" | "nexus"
+    }
+
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurerTest.groovy
index 4101343..097d063 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurerTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpClientConfigurerTest.groovy
@@ -20,20 +20,20 @@ import org.apache.http.impl.client.DefaultHttpClient
 import org.apache.http.impl.conn.ProxySelectorRoutePlanner
 import org.gradle.api.artifacts.repositories.PasswordCredentials
 import spock.lang.Specification
+import org.apache.http.params.HttpProtocolParams
 
 public class HttpClientConfigurerTest extends Specification {
     DefaultHttpClient httpClient = new DefaultHttpClient()
     PasswordCredentials credentials = Mock()
     HttpSettings httpSettings = Mock()
     HttpProxySettings proxySettings = Mock()
+    HttpClientConfigurer configurer = new HttpClientConfigurer(httpSettings)
     
     def "configures http client with no credentials or proxy"() {
-        when:
         httpSettings.credentials >> credentials
         httpSettings.proxySettings >> proxySettings
 
-        and:
-        def configurer = new HttpClientConfigurer(httpSettings)
+        when:
         configurer.configure(httpClient)
         
         then:
@@ -42,13 +42,11 @@ public class HttpClientConfigurerTest extends Specification {
     }
     
     def "configures http client with proxy credentials"() {
-        when:
         httpSettings.credentials >> credentials
         httpSettings.proxySettings >> proxySettings
         proxySettings.proxy >> new HttpProxySettings.HttpProxy("host", 1111, "domain/proxyUser", "proxyPass")
 
-        and:
-        def configurer = new HttpClientConfigurer(httpSettings)
+        when:
         configurer.configure(httpClient)
 
         then:
@@ -66,14 +64,12 @@ public class HttpClientConfigurerTest extends Specification {
     }
 
     def "configures http client with credentials"() {
-        when:
         httpSettings.credentials >> credentials
         credentials.username >> "domain/user"
         credentials.password >> "pass"
         httpSettings.proxySettings >> proxySettings
 
-        and:
-        def configurer = new HttpClientConfigurer(httpSettings)
+        when:
         configurer.configure(httpClient)
 
         then:
@@ -88,5 +84,19 @@ public class HttpClientConfigurerTest extends Specification {
         ntlmCredentials.userName == 'user'
         ntlmCredentials.password == 'pass'
         ntlmCredentials.workstation != ''
+
+        and:
+        httpClient.getRequestInterceptor(0) instanceof HttpClientConfigurer.PreemptiveAuth
+    }
+
+    def "configures http client with user agent"() {
+        httpSettings.credentials >> credentials
+        httpSettings.proxySettings >> proxySettings
+
+        when:
+        configurer.configure(httpClient)
+
+        then:
+        HttpProtocolParams.getUserAgent(httpClient.params).startsWith('Gradle')
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceListerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceListerTest.groovy
new file mode 100644
index 0000000..7eac387
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResourceListerTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.externalresource.transport.http
+
+import org.gradle.api.internal.externalresource.ExternalResource
+import spock.lang.Specification
+
+class HttpResourceListerTest extends Specification {
+
+    HttpResourceAccessor accessorMock = Mock(HttpResourceAccessor)
+    ExternalResource externalResource = Mock(ExternalResource)
+    HttpResourceLister lister = new HttpResourceLister(accessorMock)
+
+    def "consumeExternalResource closes resource after reading into stream"() {
+        setup:
+        accessorMock.getResource("http://testrepo/") >> externalResource;
+        when:
+        lister.loadResourceContent(externalResource)
+        then:
+        1 * externalResource.writeTo(_, _)
+        1 * externalResource.close()
+    }
+
+    def "list returns null if HttpAccessor returns null"(){
+        setup:
+        accessorMock.getResource("http://testrepo/")  >> null
+        expect:
+        null == lister.list("http://testrepo")
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResourceTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResourceTest.groovy
index 384f945..d93cfc5 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResourceTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/HttpResponseResourceTest.groovy
@@ -22,13 +22,14 @@ import org.apache.http.HttpResponse
 import org.apache.http.message.BasicHeader
 import org.gradle.api.internal.externalresource.ExternalResource
 import spock.lang.Specification
+import org.apache.http.HttpEntity
 
 class HttpResponseResourceTest extends Specification {
-    
+
     def sourceUrl = "http://gradle.org"
     def method = "GET"
     def response = Mock(HttpResponse)
-    
+
     def "extracts etag"() {
         given:
         addHeader(HttpHeaders.ETAG, "abc")
@@ -42,6 +43,19 @@ class HttpResponseResourceTest extends Specification {
         resource().metaData.etag == null
     }
 
+    def "is not openable more than once"() {
+        setup:
+        1 * response.entity >> Mock(HttpEntity)
+        when:
+        def resource = resource();
+        resource.openStream();
+        and:
+        resource.openStream()
+        then:
+        def ex = thrown(IOException);
+        ex.message == "Unable to open Stream as it was opened before."
+    }
+
     ExternalResource resource() {
         new HttpResponseResource(method, sourceUrl, response)
     }
@@ -51,6 +65,7 @@ class HttpResponseResourceTest extends Specification {
             1 * response.getFirstHeader(name) >> header(name, value)
         }
     }
+
     Header header(String name, String value) {
         new BasicHeader(name, value)
     }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentialsTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentialsTest.groovy
index de4a59b..626edeb 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentialsTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/externalresource/transport/http/ntlm/NTLMCredentialsTest.groovy
@@ -15,22 +15,22 @@
  */
 package org.gradle.api.internal.externalresource.transport.http.ntlm;
 
-
 import org.gradle.api.artifacts.repositories.PasswordCredentials
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
 import spock.lang.Specification
 
 public class NTLMCredentialsTest extends Specification {
-    final PasswordCredentials credentials = Mock()
+    PasswordCredentials credentials = Mock()
 
     @Rule
     public SetSystemProperties systemProperties = new SetSystemProperties()
 
     def "uses domain when encoded in username"() {
-        when:
         credentials.username >> "domain\\username"
         credentials.password >> "password"
+
+        when:
         def ntlmCredentials = new NTLMCredentials(credentials)
 
         then:
@@ -40,9 +40,10 @@ public class NTLMCredentialsTest extends Specification {
     }
 
     def "uses domain when encoded in username with forward slash"() {
-        when:
         credentials.username >> "domain/username"
         credentials.password >> "password"
+
+        when:
         def ntlmCredentials = new NTLMCredentials(credentials)
 
         then:
@@ -52,9 +53,10 @@ public class NTLMCredentialsTest extends Specification {
     }
 
     def "uses default domain when not encoded in username"() {
-        when:
         credentials.username >> "username"
         credentials.password >> "password"
+
+        when:
         def ntlmCredentials = new NTLMCredentials(credentials)
 
         then:
@@ -64,10 +66,11 @@ public class NTLMCredentialsTest extends Specification {
     }
 
     def "uses system property for domain when not encoded in username"() {
-        when:
         System.setProperty("http.auth.ntlm.domain", "domain")
         credentials.username >> "username"
         credentials.password >> "password"
+
+        when:
         def ntlmCredentials = new NTLMCredentials(credentials)
 
         then:
@@ -77,9 +80,10 @@ public class NTLMCredentialsTest extends Specification {
     }
 
     def "uses truncated hostname for workstation"() {
-        when:
         credentials.username >> "username"
         credentials.password >> "password"
+
+        when:
         def ntlmCredentials = new NTLMCredentials(credentials) {
             protected String getHostName() {
                 return "hostname.domain.org"
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParserTest.groovy
index 236b2b0..56055f4 100755
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyClassPathNotationParserTest.groovy
@@ -18,12 +18,12 @@ package org.gradle.api.internal.notations
 import org.gradle.api.artifacts.SelfResolvingDependency
 import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.ClassPathRegistry
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
 import org.gradle.api.internal.file.FileResolver
 import spock.lang.Specification
-import org.gradle.util.ClassPath
+import org.gradle.internal.classpath.ClassPath
 
 public class DependencyClassPathNotationParserTest extends Specification {
     def instantiator = Mock(Instantiator.class)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy
index cf58b1f..c5b2231 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyMapNotationParserTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal.notations;
 
 
 import org.gradle.api.artifacts.DependencyArtifact
-import org.gradle.api.internal.DirectInstantiator
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
 import spock.lang.Specification
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy
index c4b6167..387701c 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyStringNotationParserTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal.notations;
 
 
 import org.gradle.api.artifacts.DependencyArtifact
-import org.gradle.api.internal.DirectInstantiator
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
 import org.gradle.util.HelperUtil
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
index 5cf25bc..f76c398 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.notations;
 
 
-import org.gradle.api.internal.DirectInstantiator
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
 import org.gradle.api.internal.project.ProjectInternal
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/artifactory_dirlisting.html b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/artifactory_dirlisting.html
new file mode 100644
index 0000000..65659d1
--- /dev/null
+++ b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/artifactory_dirlisting.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+    <title>Index of repo1/junit/junit</title>
+</head>
+<body>
+<h1>Index of repo1/junit/junit</h1>
+<pre>Name                     Last modified      Size</pre>
+<hr/>
+<pre><a href="../">../</a>
+<a href="3.7/">3.7/</a>->                        -    -
+<a href="3.8/">3.8/</a>->                        -    -
+<a href="3.8.1/">3.8.1/</a>                    02-Jul-2012 13:40    -
+<a href="3.8.2/">3.8.2/</a>->                      -    -
+<a href="4.0/">4.0/</a>->                        -    -
+<a href="4.1/">4.1/</a>->                        -    -
+<a href="4.10/">4.10/</a>                     19-Jul-2012 09:46    -
+<a href="4.2/">4.2/</a>->                        -    -
+<a href="4.3/">4.3/</a>->                        -    -
+<a href="4.3.1/">4.3.1/</a>->                      -    -
+<a href="4.4/">4.4/</a>->                        -    -
+<a href="4.5/">4.5/</a>->                        -    -
+<a href="4.6/">4.6/</a>->                        -    -
+<a href="4.7/">4.7/</a>                      05-Jul-2012 09:00    -
+<a href="4.8/">4.8/</a>->                        -    -
+<a href="4.8.1/">4.8.1/</a>->                      -    -
+<a href="4.8.2/">4.8.2/</a>->                      -    -
+<a href="4.9/">4.9/</a>->                        -    -
+<a href="maven-metadata.xml">maven-metadata.xml</a>->         -    -
+<a href="maven-metadata.xml.md5">maven-metadata.xml.md5</a>->     -    -
+<a href="maven-metadata.xml.sha1">maven-metadata.xml.sha1</a>->    -    -
+</pre>
+<hr/>
+<address style="font-size:small;">Artifactory/2.6.1 Server at localhost Port 8081</address>
+</body>
+</html>
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/mavencentral_dirlisting.html b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/mavencentral_dirlisting.html
new file mode 100644
index 0000000..2f361df
--- /dev/null
+++ b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/mavencentral_dirlisting.html
@@ -0,0 +1,30 @@
+<html>
+<head><title>Index of /maven2/junit/junit/</title></head>
+<body bgcolor="white">
+<h1>Index of /maven2/junit/junit/</h1>
+<hr><pre><a href="../">../</a>
+    <a href="3.7/">3.7/</a>                                               07-Dec-2010 15:34                   -
+    <a href="3.8/">3.8/</a>                                               07-Dec-2010 15:34                   -
+    <a href="3.8.1/">3.8.1/</a>                                             07-Dec-2010 15:34                   -
+    <a href="3.8.2/">3.8.2/</a>                                             07-Dec-2010 15:34                   -
+    <a href="4.0/">4.0/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.1/">4.1/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.10/">4.10/</a>                                              29-Sep-2011 19:19                   -
+    <a href="4.2/">4.2/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.3/">4.3/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.3.1/">4.3.1/</a>                                             07-Dec-2010 15:34                   -
+    <a href="4.4/">4.4/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.5/">4.5/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.6/">4.6/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.7/">4.7/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.8/">4.8/</a>                                               07-Dec-2010 15:34                   -
+    <a href="4.8.1/">4.8.1/</a>                                             07-Dec-2010 15:34                   -
+    <a href="4.8.2/">4.8.2/</a>                                             07-Dec-2010 15:34                   -
+    <a href="4.9/">4.9/</a>                                               24-Aug-2011 11:32                   -
+    <a href="maven-metadata.xml">maven-metadata.xml</a>                                 29-Sep-2011 19:19                 817
+    <a href="maven-metadata.xml.md5">maven-metadata.xml.md5</a>                             29-Sep-2011 19:19                  32
+    <a href="maven-metadata.xml.sha1">maven-metadata.xml.sha1</a>                            29-Sep-2011 19:19                  40
+    </pre>
+<hr>
+</body>
+</html>
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/nexus_dirlisting.html b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/nexus_dirlisting.html
new file mode 100644
index 0000000..d1fe9a5
--- /dev/null
+++ b/subprojects/core-impl/src/test/resources/org/gradle/api/internal/externalresource/transport/http/nexus_dirlisting.html
@@ -0,0 +1,331 @@
+<!--
+
+    Sonatype Nexus (TM) Open Source Version
+    Copyright (c) 2007-2012 Sonatype, Inc.
+    All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
+
+    This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
+    which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
+
+    Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
+    of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
+    Eclipse Foundation. All other trademarks are the property of their respective owners.
+
+-->
+<html>
+  <head>
+    <title>Index of /nexus/content/repositories/central/junit/junit/</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <link rel="stylesheet" href="http://localhost:8081/nexus//style/Sonatype-content.css?2.0.6" type="text/css" media="screen" title="no title" charset="utf-8">
+  </head>
+  <body>
+    <h1>Index of /nexus/content/repositories/central/junit/junit/</h1>
+    <table cellspacing="10">
+      <tr>
+        <th align="left">Name</th>
+        <th>Last Modified</th>
+        <th>Size</th>
+        <th>Description</th>
+      </tr>
+      <tr>
+        <td>
+          <a href="../">Parent Directory</a>
+        </td>
+      </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/maven-metadata.xml">maven-metadata.xml</a>
+                          </td>
+            <td>
+              Thu Sep 29 21:19:50 CEST 2011
+            </td>
+            <td align="right">
+                              817
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/maven-metadata.xml.sha1">maven-metadata.xml.sha1</a>
+                          </td>
+            <td>
+              Thu Sep 29 21:19:50 CEST 2011
+            </td>
+            <td align="right">
+                              40
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/maven-metadata.xml.md5">maven-metadata.xml.md5</a>
+                          </td>
+            <td>
+              Thu Sep 29 21:19:50 CEST 2011
+            </td>
+            <td align="right">
+                              40
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/3.7/">3.7/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:51:57 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/3.8/">3.8/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:51:57 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/3.8.1/">3.8.1/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:51:58 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/3.8.2/">3.8.2/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:51:58 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.0/">4.0/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:51:59 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.1/">4.1/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:51:59 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.10/">4.10/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:25:49 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.2/">4.2/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:52:00 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.3/">4.3/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:52:01 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.3.1/">4.3.1/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:52:01 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.4/">4.4/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:52:02 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.5/">4.5/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:52:02 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.6/">4.6/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:52:03 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.7/">4.7/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:52:03 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.8/">4.8/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:43:51 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.8.1/">4.8.1/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:45:17 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.8.2/">4.8.2/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:45:17 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+                  <tr>
+            <td>
+                              <a href="http://localhost:8081/nexus/content/repositories/central/junit/junit/4.9/">4.9/</a>
+                          </td>
+            <td>
+              Thu Jul 19 10:43:51 CEST 2012
+            </td>
+            <td align="right">
+                               
+                          </td>
+            <td>
+               
+            </td>
+          </tr>
+            </table>
+  </body>
+</html>
\ No newline at end of file
diff --git a/subprojects/core/core.gradle b/subprojects/core/core.gradle
index 15b2e45..92af92e 100755
--- a/subprojects/core/core.gradle
+++ b/subprojects/core/core.gradle
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-import org.gradle.build.GenerateReleasesXml
-
 configurations {
     reports
 }
@@ -25,6 +23,7 @@ dependencies {
 
     publishCompile libraries.slf4j_api
     publishCompile project(":baseServices")
+    publishCompile project(":messaging")
 
     compile libraries.asm
     compile libraries.ant
@@ -37,6 +36,7 @@ dependencies {
     compile libraries.guava
     compile libraries.jcip
     compile libraries.jul_to_slf4j
+    compile module('com.googlecode.jarjar:jarjar:1.3')
 
     compile project(":cli")
     compile project(":native")
@@ -57,16 +57,20 @@ dependencies {
     reports 'css3-pie:css3-pie:1.0beta3'
 }
 
+useTestFixtures()
+
 test {
     jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m', '-XX:+HeapDumpOnOutOfMemoryError'
 }
 
 [compileGroovy, compileTestGroovy]*.groovyOptions*.fork(memoryInitialSize: '128M', memoryMaximumSize: '1G')
 
-task releasesResource(type: GenerateReleasesXml) {
-    destFile = new File(generatedResourcesDir, "org/gradle/releases.xml")
+task buildReceiptResource(type: Copy, dependsOn: rootProject.createBuildReceipt) {
+    into "$generatedResourcesDir/org/gradle"
+    from rootProject.createBuildReceipt.receiptFile
 }
-sourceSets.main.output.dir generatedResourcesDir, builtBy: releasesResource
+
+sourceSets.main.output.dir generatedResourcesDir, builtBy: buildReceiptResource
 
 task reportResources(type: Copy) {
     from configurations.reports
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy
new file mode 100644
index 0000000..0c74d12
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/dsl/DynamicObjectIntegrationTest.groovy
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2010 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 org.gradle.api.dsl
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import spock.lang.Issue
+
+class DynamicObjectIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void canAddDynamicPropertiesToProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("settings.gradle").writelns("include 'child'");
+        testDir.file("build.gradle").writelns(
+                "ext.rootProperty = 'root'",
+                "ext.sharedProperty = 'ignore me'",
+                "ext.property = 'value'",
+                "convention.plugins.test = new ConventionBean()",
+                "task rootTask",
+                "task testTask",
+                "class ConventionBean { def getConventionProperty() { 'convention' } }"
+        );
+        testDir.file("child/build.gradle").writelns(
+                "ext.childProperty = 'child'",
+                "ext.sharedProperty = 'shared'",
+                "task testTask << {",
+                "  new Reporter().checkProperties(project)",
+                "}",
+                "assert 'root' == rootProperty",
+                "assert 'root' == property('rootProperty')",
+                "assert 'root' == properties.rootProperty",
+                "assert 'child' == childProperty",
+                "assert 'child' == property('childProperty')",
+                "assert 'child' == properties.childProperty",
+                "assert 'shared' == sharedProperty",
+                "assert 'shared' == property('sharedProperty')",
+                "assert 'shared' == properties.sharedProperty",
+                "assert 'convention' == conventionProperty",
+                // Use a separate class, to isolate Project from the script
+                "class Reporter {",
+                "  def checkProperties(object) {",
+                "    assert 'root' == object.rootProperty",
+                "    assert 'child' == object.childProperty",
+                "    assert 'shared' == object.sharedProperty",
+                "    assert 'convention' == object.conventionProperty",
+                "    assert 'value' == object.property",
+                "    assert ':child:testTask' == object.testTask.path",
+                "    try { object.rootTask; fail() } catch (MissingPropertyException e) { }",
+                "  }",
+                "}"
+        );
+
+        executer.inDirectory(testDir).withTasks("testTask").run();
+    }
+
+    @Test
+    public void canAddDynamicMethodsToProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("settings.gradle").writelns("include 'child'");
+        testDir.file("build.gradle").writelns(
+                "def rootMethod(p) { 'root' + p }",
+                "def sharedMethod(p) { 'ignore me' }",
+                "convention.plugins.test = new ConventionBean()",
+                "task rootTask",
+                "task testTask",
+                "class ConventionBean { def conventionMethod(name) { 'convention' + name } }"
+        );
+        testDir.file("child/build.gradle").writelns(
+                "def childMethod(p) { 'child' + p }",
+                "def sharedMethod(p) { 'shared' + p }",
+                "task testTask << {",
+                "  new Reporter().checkMethods(project)",
+                "}",
+                // Use a separate class, to isolate Project from the script
+                "class Reporter {",
+                "  def checkMethods(object) {",
+                "    assert 'rootMethod' == object.rootMethod('Method')",
+                "    assert 'childMethod' == object.childMethod('Method')",
+                "    assert 'sharedMethod'== object.sharedMethod('Method')",
+                "    assert 'conventionMethod' == object.conventionMethod('Method')",
+                "    object.testTask { assert ':child:testTask' == delegate.path }",
+                "    try { object.rootTask { }; fail() } catch (MissingMethodException e) { }",
+                "  }",
+                "}"
+        );
+
+        executer.inDirectory(testDir).withTasks("testTask").run();
+    }
+
+    @Test
+    public void canAddMixinsToProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+convention.plugins.test = new ConventionBean()
+
+assert conventionProperty == 'convention'
+assert conventionMethod('value') == '[value]'
+
+class ConventionBean {
+    def getConventionProperty() { 'convention' }
+    def conventionMethod(String value) { "[$value]" }
+}
+'''
+
+        executer.inDirectory(testDir).run();
+    }
+
+    @Test
+    public void canAddExtensionsToProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+extensions.test = new ExtensionBean()
+
+assert test instanceof ExtensionBean
+test { it ->
+    assert it == project.test
+}
+class ExtensionBean {
+}
+'''
+
+        executer.inDirectory(testDir).run();
+    }
+
+    @Test
+    public void canAddPropertiesToProjectUsingGradlePropertiesFile() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("settings.gradle").writelns("include 'child'");
+        testDir.file("gradle.properties") << '''
+global=some value
+'''
+        testDir.file("build.gradle") << '''
+assert 'some value' == global
+assert hasProperty('global')
+assert 'some value' == property('global')
+assert 'some value' == properties.global
+assert 'some value' == project.global
+assert project.hasProperty('global')
+assert 'some value' == project.property('global')
+assert 'some value' == project.properties.global
+'''
+        testDir.file("child/gradle.properties") << '''
+global=overridden value
+'''
+        testDir.file("child/build.gradle") << '''
+assert 'overridden value' == global
+'''
+
+        executer.inDirectory(testDir).run();
+    }
+
+    @Test
+    public void canAddDynamicPropertiesToCoreDomainObjects() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class GroovyTask extends DefaultTask { }
+
+            task defaultTask {
+                ext.custom = 'value'
+            }
+            task javaTask(type: Copy) {
+                ext.custom = 'value'
+            }
+            task groovyTask(type: GroovyTask) {
+                ext.custom = 'value'
+            }
+            configurations {
+                test {
+                    ext.custom = 'value'
+                }
+            }
+            dependencies {
+                test('::name:') {
+                    ext.custom = 'value';
+                }
+                test(module('::other')) {
+                    ext.custom = 'value';
+                }
+                test(project(':')) {
+                    ext.custom = 'value';
+                }
+                test(files('src')) {
+                    ext.custom = 'value';
+                }
+            }
+            repositories {
+                ext.custom = 'repository'
+            }
+            defaultTask.custom = 'another value'
+            javaTask.custom = 'another value'
+            groovyTask.custom = 'another value'
+            assert !project.hasProperty('custom')
+            assert defaultTask.hasProperty('custom')
+            assert defaultTask.custom == 'another value'
+            assert javaTask.custom == 'another value'
+            assert groovyTask.custom == 'another value'
+            assert configurations.test.hasProperty('custom')
+            assert configurations.test.custom == 'value'
+            configurations.test.dependencies.each {
+                assert it.hasProperty('custom')
+                assert it.custom == 'value'
+                assert it.getProperty('custom') == 'value'
+            }
+            assert repositories.hasProperty('custom')
+            assert repositories.custom == 'repository'
+            repositories {
+                assert custom == 'repository'
+            }
+'''
+
+        executer.inDirectory(testDir).withTasks("defaultTask").run();
+    }
+
+    @Test
+    public void canAddMixInsToCoreDomainObjects() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class Extension { def doStuff() { 'method' } }
+            class GroovyTask extends DefaultTask { }
+
+            task defaultTask {
+                convention.plugins.custom = new Extension()
+            }
+            task javaTask(type: Copy) {
+                convention.plugins.custom = new Extension()
+            }
+            task groovyTask(type: GroovyTask) {
+                convention.plugins.custom = new Extension()
+            }
+            configurations {
+                test {
+                    convention.plugins.custom = new Extension()
+                }
+            }
+            dependencies {
+                test('::name:') {
+                    convention.plugins.custom = new Extension()
+                }
+                test(module('::other')) {
+                    convention.plugins.custom = new Extension()
+                }
+                test(project(':')) {
+                    convention.plugins.custom = new Extension()
+                }
+                test(files('src')) {
+                    convention.plugins.custom = new Extension()
+                }
+            }
+            repositories {
+                convention.plugins.custom = new Extension()
+            }
+            assert defaultTask.doStuff() == 'method'
+            assert javaTask.doStuff() == 'method'
+            assert groovyTask.doStuff() == 'method'
+            assert configurations.test.doStuff() == 'method'
+            configurations.test.dependencies.each {
+                assert it.doStuff() == 'method'
+            }
+            assert repositories.doStuff() == 'method'
+            repositories {
+                assert doStuff() == 'method'
+            }
+'''
+
+        executer.inDirectory(testDir).withTasks("defaultTask").run();
+    }
+
+    @Test
+    public void canAddExtensionsToCoreDomainObjects() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class Extension { def doStuff() { 'method' } }
+            class GroovyTask extends DefaultTask { }
+
+            task defaultTask {
+                extensions.test = new Extension()
+            }
+            task javaTask(type: Copy) {
+                extensions.test = new Extension()
+            }
+            task groovyTask(type: GroovyTask) {
+                extensions.test = new Extension()
+            }
+            configurations {
+                test {
+                    extensions.test = new Extension()
+                }
+            }
+            dependencies {
+                test('::name:') {
+                    extensions.test = new Extension()
+                }
+                test(module('::other')) {
+                    extensions.test = new Extension()
+                }
+                test(project(':')) {
+                    extensions.test = new Extension()
+                }
+                test(files('src')) {
+                    extensions.test = new Extension()
+                }
+            }
+            repositories {
+                extensions.test = new Extension()
+            }
+            assert defaultTask.test instanceof Extension
+            assert javaTask.test instanceof Extension
+            assert groovyTask.test instanceof Extension
+            assert configurations.test.test instanceof Extension
+            configurations.test.dependencies.each {
+                assert it.test instanceof Extension
+            }
+            assert repositories.test instanceof Extension
+            repositories {
+                assert test instanceof Extension
+            }
+'''
+
+        executer.inDirectory(testDir).withTasks("defaultTask").run();
+    }
+
+    @Test
+    public void mixesDslMethodsIntoCoreDomainObjects() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class GroovyTask extends DefaultTask {
+                def String prop
+                void doStuff(Action<Task> action) { action.execute(this) }
+            }
+            tasks.withType(GroovyTask) { conventionMapping.prop = { '[default]' } }
+            task test(type: GroovyTask)
+            assert test.prop == '[default]'
+            test {
+                description 'does something'
+                prop 'value'
+            }
+            assert test.description == 'does something'
+            assert test.prop == 'value'
+            test.doStuff {
+                prop = 'new value'
+            }
+            assert test.prop == 'new value'
+'''
+
+        executer.inDirectory(testDir).withTasks("test").run();
+    }
+
+    @Test
+    void canAddExtensionsToDynamicExtensions() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class Extension {
+                String name
+                Extension(String name) {
+                    this.name = name
+                }
+            }
+
+            project.extensions.create("l1", Extension, "l1")
+            project.l1.extensions.create("l2", Extension, "l2")
+            project.l1.l2.extensions.create("l3", Extension, "l3")
+
+            task test << {
+                assert project.l1.name == "l1"
+                assert project.l1.l2.name == "l2"
+                assert project.l1.l2.l3.name == "l3"
+            }
+        '''
+
+        executer.inDirectory(testDir).withTasks("test").run();
+    }
+
+    @Test
+    public void canInjectMethodsFromParentProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("settings.gradle").writelns("include 'child'");
+        testDir.file("build.gradle").writelns(
+                "subprojects {",
+                "  ext.injectedMethod = { project.name }",
+                "}"
+        );
+        testDir.file("child/build.gradle").writelns(
+                "task testTask << {",
+                "   assert injectedMethod() == 'child'",
+                "}"
+        );
+
+        executer.inDirectory(testDir).withTasks("testTask").run();
+    }
+    
+    @Test void canAddNewPropertiesViaTheAdhocNamespace() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("build.gradle") << """
+            assert !hasProperty("p1")
+
+            ext {
+                set "p1", 1
+            }
+
+            assert p1 == 1
+            assert properties.p1 == 1
+            assert ext.p1 == 1
+            assert hasProperty("p1")
+            assert property("p1") == 1
+            assert getProperty("p1") == 1
+            assert ext.getProperty("p1") == 1
+
+            p1 = 2
+            assert p1 == 2
+            assert ext.p1 == 2
+
+            task run << { task ->
+                assert !task.hasProperty("p1")
+
+                ext {
+                    set "p1", 1
+                }
+                assert p1 == 1
+                assert task.hasProperty("p1")
+                assert task.property("p1") == 1
+
+                p1 = 2
+                assert p1 == 2
+                assert ext.p1 == 2
+            }
+        """
+        
+        executer.withTasks("run").run()
+    }
+
+    @Test void warnsWhenNewPropertiesAreAddedDirectlyOnTargetObject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("build.gradle") << """
+            assert !hasProperty("p1")
+
+            p1 = 1
+
+            assert p1 == 1
+            assert ext.p1 == 1
+
+            task run << { task ->
+                p2 = 2
+
+                assert p2 == 2
+                assert task.ext.p2 == 2
+            }
+        """
+
+        executer.withDeprecationChecksDisabled()
+        def result = executer.withTasks("run").run()
+
+        assert result.output.contains('Dynamic properties are deprecated: http://gradle.org/docs/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html')
+        assert result.output.contains('Deprecated dynamic property: "p1" on "root project ')
+        assert result.output.contains('Deprecated dynamic property: "p2" on "task \':run\'", value: "2".')
+    }
+
+    @Issue("GRADLE-2163")
+    @Test void canDecorateBooleanPrimitiveProperties() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("build.gradle") << """
+            class CustomBean {
+                boolean b
+            }
+
+            // best way to decorate right now
+            extensions.create('bean', CustomBean)
+
+            task run << {
+                assert bean.b == false
+                bean.conventionMapping.b = { true }
+                assert bean.b == true
+            }
+        """
+
+        executer.withTasks("run").run()
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy
new file mode 100644
index 0000000..ec5435a
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveIntegrationTest.groovy
@@ -0,0 +1,531 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.TestFile
+import org.junit.Rule
+import static org.hamcrest.Matchers.equalTo
+
+public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
+
+    def canCopyFromAZip() {
+        given:
+        createZip('test.zip') {
+            subdir1 {
+                file 'file1.txt'
+            }
+            subdir2 {
+                file 'file2.txt'
+                file 'file2.xml'
+            }
+        }
+        and:
+        buildFile << '''
+            task copy(type: Copy) {
+                from zipTree('test.zip')
+                exclude '**/*.xml'
+                into 'dest'
+            }
+'''
+        when:
+        run 'copy'
+        then:
+        file('dest').assertHasDescendants('subdir1/file1.txt', 'subdir2/file2.txt')
+    }
+
+    def cannotCreateAnEmptyTar() {
+        given:
+        buildFile << """
+            task tar(type: Tar) {
+                from 'test'
+                destinationDir = buildDir
+                archiveName = 'test.tar'
+            }
+            """
+        when:
+        run "tar"
+
+        then:
+        file('build/test.tar').assertDoesNotExist()
+    }
+
+
+
+    def canCopyFromATar() {
+        given:
+        createTar('test.tar') {
+            subdir1 {
+                file 'file1.txt'
+            }
+            subdir2 {
+                file 'file2.txt'
+                file 'file2.xml'
+            }
+        }
+        and:
+        buildFile << '''
+            task copy(type: Copy) {
+                from tarTree('test.tar')
+                exclude '**/*.xml'
+                into 'dest'
+            }
+'''
+        when:
+        run 'copy'
+        then:
+        file('dest').assertHasDescendants('subdir1/file1.txt', 'subdir2/file2.txt')
+    }
+
+    def "handles gzip compressed tars"() {
+        given:
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+                file '2.txt'
+            }
+        }
+        tar.tgzTo(file('test.tgz'))
+        and:
+        buildFile << '''
+            task copy(type: Copy) {
+                from tarTree('test.tgz')
+                exclude '**/2.txt'
+                into 'dest'
+            }
+'''
+        when:
+        run 'copy'
+        then:
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
+    def "allows user to provide a custom resource for the tarTree"() {
+        given:
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+            }
+        }
+        tar.tarTo(file('test.tar'))
+        and:
+        buildFile << '''
+            def res = new ReadableResource() {
+                InputStream read() { new FileInputStream(file('test.tar')) }
+                String getBaseName() { "foo" }
+                URI getURI() { new java.net.URI("foo") }
+                String getDisplayName() { "The foo" }
+            }
+
+            task copy(type: Copy) {
+                from tarTree(res)
+                into 'dest'
+            }
+'''
+        when:
+        run 'copy'
+        then:
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
+    def "handles bzip2 compressed tars"() {
+        given:
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+                file '2.txt'
+            }
+        }
+        tar.tbzTo(file('test.tbz2'))
+        and:
+        buildFile << '''
+            task copy(type: Copy) {
+                from tarTree('test.tbz2')
+                exclude '**/2.txt'
+                into 'dest'
+            }
+'''
+        when:
+        run 'copy'
+        then:
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
+    def "knows compression of the tar"() {
+        given:
+        TestFile tar = file()
+        tar.tbzTo(file('test.tbz2'))
+        and:
+        buildFile << '''
+            task myTar(type: Tar) {
+                assert compression == Compression.NONE
+
+                compression = Compression.GZIP
+                assert compression == Compression.GZIP
+
+                compression = Compression.BZIP2
+                assert compression == Compression.BZIP2
+            }
+'''
+
+        expect:
+        run 'myTar'
+    }
+
+    def "can choose compression method for tarTree"() {
+        given:
+        TestFile tar = file()
+        tar.create {
+            someDir {
+                file '1.txt'
+                file '2.txt'
+            }
+        }
+        //file extension is non-standard:
+        tar.tbzTo(file('test.ext'))
+
+        and:
+        buildFile << '''
+            task copy(type: Copy) {
+                from tarTree(resources.bzip2('test.ext'))
+                exclude '**/2.txt'
+                into 'dest'
+            }
+'''
+        when:
+        run 'copy'
+        then:
+        file('dest').assertHasDescendants('someDir/1.txt')
+    }
+
+    @Rule public final TestResources resources = new TestResources()
+
+    def "tarTreeFailsGracefully"() {
+        given:
+        buildFile << '''
+            task copy(type: Copy) {
+                //the input file comes from the resources to make sure it is truly improper 'tar', see GRADLE-1952
+                from tarTree('compressedTarWithWrongExtension.tar')
+                into 'dest'
+            }
+'''
+        when:
+        def failure = runAndFail('copy')
+        then:
+        assert failure.error.contains("Unable to expand TAR")
+        assert failure.error.contains("compression based on the file extension")
+    }
+
+    def cannotCreateAnEmptyZip() {
+        given:
+        buildFile << '''
+            task zip(type: Zip) {
+                from 'test'
+                destinationDir = buildDir
+                archiveName = 'test.zip'
+            }
+        '''
+        when:
+        run 'zip'
+        then:
+        file('build/test.zip').assertDoesNotExist()
+    }
+
+    def canCreateAZipArchive() {
+        given:
+        createDir('test') {
+            dir1 {
+                file('file1.txt').write("abc")
+            }
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+                file 'script.sh'
+            }
+        }
+        and:
+        buildFile << '''
+            task zip(type: Zip) {
+                into('prefix') {
+                    from 'test'
+                    include '**/*.txt'
+                    rename { "renamed_$it" }
+                    filter { "[$it]" }
+                }
+                into('scripts') {
+                    from 'test'
+                    include '**/*.sh'
+                }
+                destinationDir = buildDir
+                archiveName = 'test.zip'
+            }
+        '''
+        when:
+        run 'zip'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'prefix/dir1/renamed_file1.txt',
+                'prefix/renamed_file1.txt',
+                'prefix/dir2/renamed_file2.txt',
+                'scripts/dir2/script.sh')
+
+        expandDir.file('prefix/dir1/renamed_file1.txt').assertContents(equalTo('[abc]'))
+    }
+
+    def canCreateATarArchive() {
+        given:
+        createDir('test') {
+            dir1 {
+                file('file1.txt').write 'abc'
+            }
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+                file 'script.sh'
+            }
+        }
+        and:
+        buildFile << '''
+            task tar(type: Tar) {
+                from('test') {
+                    include '**/*.txt'
+                    filter { "[$it]" }
+                }
+                from('test') {
+                    include '**/*.sh'
+                    into 'scripts'
+                }
+                destinationDir = buildDir
+                archiveName = 'test.tar'
+            }
+'''
+        when:
+        run 'tar'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.tar').untarTo(expandDir)
+        expandDir.assertHasDescendants('dir1/file1.txt', 'file1.txt', 'dir2/file2.txt', 'scripts/dir2/script.sh')
+
+        expandDir.file('dir1/file1.txt').assertContents(equalTo('[abc]'))
+    }
+
+    def canCreateATgzArchive() {
+        given:
+        createDir('test') {
+            dir1 {
+                file 'file1.txt'
+            }
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+                file 'ignored.xml'
+            }
+        }
+        and:
+        buildFile << '''
+            task tar(type: Tar) {
+                compression = Compression.GZIP
+                from 'test'
+                include '**/*.txt'
+                destinationDir = buildDir
+                archiveName = 'test.tgz'
+            }
+'''
+        when:
+        run 'tar'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.tgz').untarTo(expandDir)
+        expandDir.assertHasDescendants('dir1/file1.txt', 'file1.txt', 'dir2/file2.txt')
+    }
+
+    def canCreateATbzArchive() {
+        given:
+        createDir('test') {
+            dir1 {
+                file 'file1.txt'
+            }
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+                file 'ignored.xml'
+            }
+        }
+        and:
+        buildFile << '''
+            task tar(type: Tar) {
+                compression = Compression.BZIP2
+                from 'test'
+                include '**/*.txt'
+                destinationDir = buildDir
+                archiveName = 'test.tbz2'
+            }
+'''
+        when:
+        run 'tar'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.tbz2').untarTo(expandDir)
+        expandDir.assertHasDescendants('dir1/file1.txt', 'file1.txt', 'dir2/file2.txt')
+    }
+
+    def canCreateArchivesAndExplodedImageFromSameSpec() {
+        given:
+        createDir('test') {
+            dir1 {
+                file 'file1.txt'
+                file 'ignored.xml'
+            }
+            dir2 {
+                dir3 { file 'file2.txt' }
+                file 'ignored.xml'
+            }
+        }
+        and:
+        buildFile << '''
+            def distImage = copySpec {
+                include '**/*.txt'
+                from('test/dir1') {
+                    into 'lib'
+                }
+                from('test/dir2') {
+                    into 'src'
+                }
+            }
+            task copy(type: Copy) {
+                into 'build/exploded'
+                with distImage
+            }
+            task zip(type: Zip) {
+                destinationDir = file('build')
+                archiveName = 'test.zip'
+                into 'prefix'
+                with distImage
+            }
+'''
+        when:
+        run 'copy', 'zip'
+        then:
+        file('build/exploded').assertHasDescendants(
+                'lib/file1.txt', 'src/dir3/file2.txt'
+        )
+        def expandDir = file('expanded')
+        file('build/test.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants('prefix/lib/file1.txt', 'prefix/src/dir3/file2.txt')
+    }
+
+    def canCreateExplodedImageFromArchiveTask() {
+        given:
+        createDir('test') {
+            dir1 {
+                file 'file1.txt'
+                file 'ignored.xml'
+            }
+            dir2 {
+                dir3 { file 'file2.txt' }
+                file 'ignored.xml'
+            }
+        }
+        and:
+        buildFile << '''
+            task zip(type: Zip) {
+                destinationDir = file('build')
+                archiveName = 'test.zip'
+                into 'prefix'
+                from 'test'
+                include '**/*.txt'
+            }
+            task explodedZip(type: Copy) {
+                into 'build/exploded'
+                with zip
+            }
+            task copyFromRootSpec(type: Copy) {
+                into 'build/copy'
+                with zip.rootSpec
+            }
+        '''
+        when:
+        run 'explodedZip', 'copyFromRootSpec'
+        then:
+        file('build/exploded').assertHasDescendants(
+                'prefix/dir1/file1.txt', 'prefix/dir2/dir3/file2.txt'
+        )
+        file('build/copy').assertHasDescendants(
+                'prefix/dir1/file1.txt', 'prefix/dir2/dir3/file2.txt'
+        )
+    }
+
+    def canMergeArchivesIntoAnotherZip() {
+        given:
+        createZip('test.zip') {
+            shared {
+                file 'zip.txt'
+            }
+            zipdir1 {
+                file 'file1.txt'
+            }
+        }
+        createTar('test.tar') {
+            shared {
+                file 'tar.txt'
+            }
+            tardir1 {
+                file 'file1.txt'
+            }
+        }
+        createDir('test') {
+            shared {
+                file 'dir.txt'
+            }
+            dir1 {
+                file 'file1.txt'
+            }
+        }
+        and:
+        buildFile << '''
+        task zip(type: Zip) {
+            from zipTree('test.zip')
+            from tarTree('test.tar')
+            from fileTree('test')
+            destinationDir = buildDir
+            archiveName = 'test.zip'
+        }
+        '''
+        when:
+        run 'zip'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants('shared/zip.txt', 'zipdir1/file1.txt', 'shared/tar.txt', 'tardir1/file1.txt', 'shared/dir.txt', 'dir1/file1.txt')
+    }
+
+    def createTar(String name, Closure cl) {
+        TestFile tarRoot = file("${name}.root")
+        TestFile tar = file(name)
+        tarRoot.create(cl)
+        tarRoot.tarTo(tar)
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy
index e4ee2f4..14304bd 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/ArchiveTaskPermissionsIntegrationTest.groovy
@@ -17,136 +17,143 @@
 package org.gradle.api.tasks
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.util.TemporaryFolder
-import org.junit.Rule
-import org.gradle.util.TextUtil
+import org.gradle.util.Requires
+import org.gradle.util.TestFile
+import org.gradle.util.TestPrecondition
+import spock.lang.Unroll
+import static org.junit.Assert.assertTrue
 
 class ArchiveTaskPermissionsIntegrationTest extends AbstractIntegrationSpec {
-    @Rule TemporaryFolder tmpDir = new TemporaryFolder()
 
-    def "permissions are preserved, overridden by type, and overridden by copy action"() {
-        def referenceArchive = createReferenceArchiveWithPermissions(0666)
-
-        def buildScript = file("build.gradle") << """
-            $assertModesFunction
-
-            task copy(type: Tar) {
-                from tarTree('${TextUtil.escapeString(referenceArchive.absolutePath)}')
-                dirMode = 0777
-                eachFile {
-                    if (it.name == 'script') {
-                        it.mode = 0123;
-                    }
-                }
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    @Unroll
+    def "file and directory permissions are preserved when using #taskName task"() {
+        given:
+        createDir('parent') {
+            child {
+                mode = 0777
+                file('reference.txt').mode = 0746
             }
-
-            task test(dependsOn: copy) << {
-                assertModes(tarTree(copy.archivePath), [
-                    file: '666',    // preserved
-                    script: '123',  // overridden by dirMode
-                    folder: '777'   // overridden by copy action
-                ])
+        }
+        def archName = "test.${taskName.toLowerCase()}"
+        and:
+        buildFile << """
+            task pack(type: $taskName) {
+                archiveName = "$archName"
+                from 'parent'
             }
-        """
-
+            """
         when:
-        executer.usingBuildScript(buildScript)
-            .withTasks('test')
-            .run()
-
+        run "pack"
+        file(archName).usingNativeTools()."$unpackMethod"(file("build"))
         then:
-        noExceptionThrown()
+        file("build/child").mode == 0777
+        file("build/child/reference.txt").mode == 0746
+        where:
+        taskName | unpackMethod
+        "Zip"    | "unzipTo"
+        "Tar"    | "untarTo"
     }
 
-    def "expected permissions are exposed to copy action"() {
-        def referenceArchive = createReferenceArchiveWithPermissions(0666)
-
-        def buildScript = file("build.gradle") << """
-            import static java.lang.Integer.toOctalString
-            $assertModesFunction
-
-            def preservedModes = [:]
-            task copyPreserved(type: Tar) {
-                from tarTree('${TextUtil.escapeString(referenceArchive.absolutePath)}')
-                eachFile {
-                    preservedModes[it.name] = toOctalString(it.mode)
-                }
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    @Unroll
+    def "file and directory permissions can be overridden in #taskName task"() {
+        given:
+        createDir('parent') {
+            child {
+                mode = 0766
+                file('reference.txt').mode = 0777
             }
-
-            def overriddenModes = [:]
-            task copyOverridden(type: Tar) {
-                from tarTree('${TextUtil.escapeString(referenceArchive.absolutePath)}')
-                fileMode = 0123
-                eachFile {
-                    overriddenModes[it.name] = toOctalString(it.mode)
+        }
+        def archName = "test.${taskName.toLowerCase()}"
+
+        and:
+        buildFile << """
+                task pack(type: $taskName) {
+                    archiveName = "$archName"
+                    fileMode = 0774
+                    dirMode = 0756
+                    from 'parent'
                 }
-            }
+                """
+        when:
+        run "pack"
+        and:
+        file(archName).usingNativeTools()."$unpackMethod"(file("build"))
+        then:
+        file("build/child").mode == 0756
+        file("build/child/reference.txt").mode == 0774
+        where:
+        taskName | unpackMethod
+        "Zip"    | "unzipTo"
+        "Tar"    | "untarTo"
 
-            task test(dependsOn: [copyPreserved, copyOverridden]) << {
-                assert preservedModes == [file: "666", script: "666"]
-                assert overriddenModes == [file: "123", script: "123"]
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    @Unroll
+    def "file and directory permissions are preserved for unpacked #taskName archives"() {
+        given:
+        TestFile testDir = createDir('testdir') {
+            mode = 0753
+            file('reference.txt').mode = 0762
+        }
+        def archName = "test.${taskName.toLowerCase()}"
+        testDir.usingNativeTools()."$packMethod"(file(archName))
+        and:
+        buildFile << """
+            task unpack(type: Copy) {
+                from $treeMethod("$archName")
+                into 'unpacked'
             }
-        """
+            """
 
         when:
-        executer.usingBuildScript(buildScript)
-            .withTasks('test')
-            .run()
-
+        run "unpack"
+        and:
         then:
-        noExceptionThrown()
+        file("unpacked/testdir").mode == 0753
+        file("unpacked/testdir/reference.txt").mode == 0762
+        where:
+        taskName | packMethod | treeMethod
+        "Zip"    | "zipTo"    | "zipTree"
+        "Tar"    | "tarTo"    | "tarTree"
     }
 
-    /*
-     * Creates a TAR archive with three files, 'file', 'script' and 'folder'. These files
-     * are used as reference data for the tests. The TAR archive is used to make the tests
-     * independent of the file system, which may not support Unix permissions.
-     */
-    private File createReferenceArchiveWithPermissions(int mode) {
-        def archive = tmpDir.file("reference.tar")
-        def archiveTmp = tmpDir.createDir('reference')
-
-        archiveTmp.createFile("file")
-        archiveTmp.createFile("script")
-        archiveTmp.createDir("folder")
-
-        // create the archive, with correct permissions using a build script
-        def script = file("create-reference-archive.gradle") << """
-            import static java.lang.Integer.toOctalString
-            $assertModesFunction
-
-            task createReference(type: Tar) { 
-                destinationDir = file('${TextUtil.escapeString(tmpDir.dir)}')
-                archiveName = '${TextUtil.escapeString(archive.name)}'
-                from file('${TextUtil.escapeString(archiveTmp.absolutePath)}')
-                fileMode = $mode
-                dirMode = $mode
+    @Requires(TestPrecondition.WINDOWS)
+    @Unroll
+    def "file and directory permissions are not preserved when dealing with #taskName archives on OS with no permission support"() {
+        given:
+        TestFile testDir = createDir('root') {
+            def testDir = testdir{
+                def testFile = file('reference.txt')
+                assertTrue testFile.setReadOnly()
             }
-
-            task verifyReference(dependsOn: createReference) << {
-                assertModes(tarTree(file('${TextUtil.escapeString(archive.absolutePath)}')), [
-                    file: toOctalString($mode),
-                    script: toOctalString($mode),
-                    folder: toOctalString($mode)
-                ])
+            testDir.setReadOnly()
+        }
+        testDir.setReadOnly()
+        def archName = "test.${taskName.toLowerCase()}"
+        testDir."$packMethod"(file(archName))
+        and:
+        buildFile << """
+            task unpack(type: Copy) {
+                from $treeMethod("$archName")
+                into 'unpacked'
             }
-        """
+            """
+        when:
+        run "unpack"
+        then:
 
-        executer.usingBuildScript(script)
-            .withTasks('verifyReference')
-            .run()
+        def testOutputFile = file("unpacked/testdir/reference.txt")
+        testOutputFile.canWrite()
 
-        archive
-    }
+        def testOutputDir = file("unpacked/testdir")
+        testOutputDir.canWrite()
 
-    // script fragment for extracting & checking files/modes from a FileTree
-    def assertModesFunction = """
-        def assertModes = { files, expectedModes ->
-            def actualModes = [:]
-            files.visit {
-                actualModes[it.name] = Integer.toOctalString(it.mode)
-            }
-            assert expectedModes == actualModes
-        }
-    """
-}
+        where:
+        taskName | packMethod | treeMethod
+        "Zip"    | "zipTo"    | "zipTree"
+        "Tar"    | "tarTo"    | "tarTree"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy
index a31546d..c6ce410 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyPermissionsIntegrationTest.groovy
@@ -18,21 +18,22 @@ package org.gradle.api.tasks
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.util.Requires
+import org.gradle.util.TestFile
 import org.gradle.util.TestPrecondition
+import static org.junit.Assert.assertTrue
 
- at Requires(TestPrecondition.FILE_PERMISSIONS)
 class CopyPermissionsIntegrationTest extends AbstractIntegrationSpec {
 
-
-    def "file permissions of a file are preserved in copy action"() {
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "file permissions are preserved in copy action"() {
         given:
-        def testSourceFile = file("reference.txt") << 'test file"'
-        testSourceFile.permissions = mode
+        def testSourceFile = file(testFileName)
+        testSourceFile << "test file content"
+        testSourceFile.mode = mode
         and:
         buildFile << """
-        import static java.lang.Integer.toOctalString
         task copy(type: Copy) {
-            from "reference.txt"
+            from "${testSourceFile.absolutePath}"
             into ("build/tmp")
         }
         """
@@ -40,49 +41,87 @@ class CopyPermissionsIntegrationTest extends AbstractIntegrationSpec {
         when:
         run "copy"
         then:
-        file("build/tmp/reference.txt").permissions == mode
+        file("build/tmp/${testFileName}").mode == mode
+        where:
+        mode << [0746, 0746]
+        testFileName << ["reference.txt", "\u0627\u0644\u0627\u0655\u062F\u0627\u0631\u0629.txt"]
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "file permissions can be modfied with eachFile closure"() {
+        given:
+        def testSourceFile = file("reference.txt") << 'test file"'
+        testSourceFile.mode = 0746
+        and:
+        buildFile << """
+            task copy(type: Copy) {
+                from "reference.txt"
+                eachFile {
+		            it.setMode(0755)
+	            }
+                into ("build/tmp")
+            }
+            """
+        when:
+        run "copy"
+        then:
+        file("build/tmp/reference.txt").mode == 0755
+    }
+
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "directory permissions are preserved in copy action"() {
+        given:
+        TestFile parent = getTestDir().createDir("testparent")
+        TestFile child = parent.createDir("testchild")
+        child.file("reference.txt") << "test file"
+
+        child.mode = mode
+        and:
+        buildFile << """
+            task copy(type: Copy) {
+                from "testparent"
+                into ("build/tmp")
+            }
+            """
+        when:
+        run "copy"
+        then:
+        file("build/tmp/testchild").mode == mode
         where:
-        mode << ['rwxr--r-x']
+        mode << [0755, 0776]
     }
 
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
     def "fileMode can be modified in copy task"() {
         given:
 
         file("reference.txt") << 'test file"'
+        file("reference.txt").mode = 0777
         and:
         buildFile << """
-             import static java.lang.Integer.toOctalString
              task copy(type: Copy) {
                  from "reference.txt"
                  into ("build/tmp")
                  fileMode = $mode
              }
-
-            ${verifyPermissionsTask(mode)}
             """
-
         when:
-        run "verifyPermissions"
+        run "copy"
 
         then:
-        noExceptionThrown()
+        file("build/tmp/reference.txt").mode == mode
 
         where:
-        mode << [0755, 0777]
-
+        mode << [0755, 0776]
     }
 
-
-
-
-
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
     def "fileMode can be modified in copy action"() {
         given:
         file("reference.txt") << 'test file"'
 
         and:
         buildFile << """
-            import static java.lang.Integer.toOctalString
             task copy << {
                 copy {
                     from 'reference.txt'
@@ -90,27 +129,62 @@ class CopyPermissionsIntegrationTest extends AbstractIntegrationSpec {
                     fileMode = $mode
                 }
             }
-
-            ${verifyPermissionsTask(mode)}
             """
 
         when:
-        run "verifyPermissions"
+        run "copy"
 
         then:
-        noExceptionThrown()
-
+        file("build/tmp/reference.txt").mode == mode
         where:
-        mode << [0755, 0777]
+        mode << [0755, 0776]
 
     }
 
-    String verifyPermissionsTask(int mode) {
-        """task verifyPermissions(dependsOn: copy) << {
-                fileTree("build/tmp").visit{
-                    assert toOctalString($mode) == toOctalString(it.mode)
-                }
-           }
+    @Requires(TestPrecondition.FILE_PERMISSIONS)
+    def "dirMode can be modified in copy task"() {
+        given:
+        TestFile parent = getTestDir().createDir("testparent")
+        TestFile child = parent.createDir("testchild")
+        child.file("reference.txt") << "test file"
+
+        child.mode = 0777
+        and:
+        buildFile << """
+            task copy(type: Copy) {
+                from "testparent"
+                into ("build/tmp")
+                dirMode = $mode
+            }
+            """
+        when:
+        run "copy"
+        then:
+        file("build/tmp/testchild").mode == mode
+        where:
+        mode << [0755, 0776]
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "file permissions are not preserved on OS without permission support"() {
+        given:
+        def testSourceFile = file("reference.txt") << 'test file"'
+        assertTrue testSourceFile.setReadOnly()
+        and:
+        buildFile << """
+        task copy(type: Copy) {
+            from "reference.txt"
+            into ("build/tmp")
+        }
         """
+        when:
+        withDebugLogging()
+        run "copy"
+        then:
+        def testTargetFile = file("build/tmp/reference.txt")
+        testTargetFile.exists()
+        testTargetFile.canWrite()
     }
+
+
 }
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy
new file mode 100644
index 0000000..afb84fb
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+
+class CopyTaskIntegrationSpec extends AbstractIntegrationSpec {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2181")
+    def "can copy files with unicode characters in name with non-unicode platform encoding"() {
+        given:
+        def weirdFileName = "القيادة والسيطرة - الإدارة.lnk"
+
+        buildFile << """
+            task copyFiles << {
+                copy {
+                    from 'res'
+                    into 'build/resources'
+                }
+            }
+        """
+
+        file("res", weirdFileName) << "foo"
+
+        when:
+        executer.withDefaultCharacterEncoding("ISO-8859-1").withTasks("copyFiles")
+        executer.run()
+
+        then:
+        file("build/resources", weirdFileName).exists()
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2181")
+    def "can copy files with unicode characters in name with default platform encoding"() {
+        given:
+        def weirdFileName = "القيادة والسيطرة - الإدارة.lnk"
+
+        buildFile << """
+            task copyFiles << {
+                copy {
+                    from 'res'
+                    into 'build/resources'
+                }
+            }
+        """
+
+        file("res", weirdFileName) << "foo"
+
+        when:
+        executer.withTasks("copyFiles").run()
+
+        then:
+        file("build/resources", weirdFileName).exists()
+    }
+}
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy
new file mode 100644
index 0000000..37ed2dd
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012 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 org.gradle.groovy.scripts
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class StatementLabelsIntegrationTest extends AbstractIntegrationSpec {
+    def setup() {
+        executer.withDeprecationChecksDisabled()
+    }
+
+    def "use of statement label in build script is flagged"() {
+        buildFile << """
+version: '1.0'
+        """
+
+        expect:
+        succeeds("tasks")
+        output.contains("Usage of statement labels in build scripts has been deprecated")
+        output.contains("version")
+
+        // try again to make sure that warning sticks if build script is cached
+        executer.withDeprecationChecksDisabled()
+        succeeds("tasks")
+        output.contains("Usage of statement labels in build scripts has been deprecated")
+        output.contains("version")
+    }
+
+    def "all usages of statement labels are flagged"() {
+        buildFile << """
+version: '1.0'
+group = "foo"
+description: "bar"
+        """
+
+        expect:
+        succeeds("tasks")
+        output.contains("Usage of statement labels in build scripts has been deprecated")
+        output.contains("version")
+        !output.contains("group")
+        output.contains("description")
+    }
+
+    def "nested use of statement label in build script is flagged"() {
+        buildFile << """
+def foo() {
+    1.times {
+      for (i in 1..1) {
+        another: "label"
+      }
+    }
+}
+        """
+
+        expect:
+        succeeds("tasks")
+        output.contains("Usage of statement labels in build scripts has been deprecated")
+        output.contains("label")
+    }
+
+    def "use of statement label in class inside build script is allowed"() {
+        buildFile << """
+class Foo {
+  def bar() {
+    mylabel:
+    def x = 1
+  }
+}
+        """
+
+        expect:
+        succeeds("tasks")
+        executer.assertOutputHasNoDeprecationWarnings(result)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArchiveIntegrationTest/tarTreeFailsGracefully/compressedTarWithWrongExtension.tar b/subprojects/core/src/integTest/resources/org/gradle/api/tasks/ArchiveIntegrationTest/tarTreeFailsGracefully/compressedTarWithWrongExtension.tar
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/ArchiveIntegrationTest/tarTreeFailsGracefully/compressedTarWithWrongExtension.tar
rename to subprojects/core/src/integTest/resources/org/gradle/api/tasks/ArchiveIntegrationTest/tarTreeFailsGracefully/compressedTarWithWrongExtension.tar
diff --git a/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java b/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
index 874edd1..913c4af 100644
--- a/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
+++ b/subprojects/core/src/main/groovy/org/gradle/CacheUsage.java
@@ -20,7 +20,7 @@ import org.gradle.api.InvalidUserDataException;
 /**
  * <p>{@code CacheUsage} specifies how compiled scripts should be cached.</p>
  *
- * @deprecated This enum has been deprecated. Use StartParameter#isRerunTasks() and StartParameter#isRecompileScripts() instead.
+ * @deprecated This enum has been deprecated. Use {@link StartParameter#isRerunTasks()} and {@link StartParameter#isRecompileScripts()} instead.
  * @author Hans Dockter
  */
 @Deprecated
diff --git a/subprojects/core/src/main/groovy/org/gradle/RefreshOptions.java b/subprojects/core/src/main/groovy/org/gradle/RefreshOptions.java
index 9df6749..6e75e3e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/RefreshOptions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/RefreshOptions.java
@@ -25,7 +25,7 @@ import java.util.List;
 
 /**
  * The options supplied by a user to refresh dependencies and other external resources.
- * @deprecated Use Commandline Option '--refresh-dependencies' instead.
+ * @deprecated Use {@link StartParameter#setRefreshDependencies(boolean)} instead.
  */
 @Deprecated
 public class RefreshOptions implements Serializable {
diff --git a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
index 79c5591..ddc45ad 100644
--- a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
@@ -354,7 +354,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
 
     /**
      *  Returns the configured CacheUsage.
-     *  @deprecated Use #isRecompileScripts and/or #isRerunTasks instead.
+     *  @deprecated Use {@link #isRecompileScripts} and/or {@link #isRerunTasks} instead.
      * */
     @Deprecated
     public CacheUsage getCacheUsage() {
@@ -363,7 +363,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
 
     /**
      *  Sets the Cache usage.
-     *  @deprecated Use #setRecompileScripts and/or #setRerunTasks instead.
+     *  @deprecated Use {@link #setRecompileScripts} and/or {@link #setRerunTasks} instead.
      * */
     @Deprecated
     public void setCacheUsage(CacheUsage cacheUsage) {
@@ -381,7 +381,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     /**
      * Returns task optimization disabled flag.
      *
-     * @deprecated Use #isRerunTasks instead.
+     * @deprecated Use {@link #isRerunTasks} instead.
      * */
     @Deprecated
     public boolean isNoOpt() {
@@ -391,8 +391,8 @@ public class StartParameter extends LoggingConfiguration implements Serializable
    /**
     * Get task optimization disabled.
     *
-    * @param noOpt The boolean value for disabling task optimsation.
-    * @deprecated Use #setRefreshDependencies(boolean) instead.
+    * @param noOpt The boolean value for disabling task optimization.
+    * @deprecated Use {@link #setRefreshDependencies(boolean)} instead.
     */
     @Deprecated
     public void setNoOpt(boolean noOpt) {
@@ -527,7 +527,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
 
     /**
      * Supplies the refresh options to use for the build.
-     * @deprecated Use #setRefreshDependencies(boolean) instead.
+     * @deprecated Use {@link #setRefreshDependencies(boolean)} instead.
      */
     @Deprecated
     public void setRefreshOptions(RefreshOptions refreshOptions) {
@@ -536,23 +536,22 @@ public class StartParameter extends LoggingConfiguration implements Serializable
 
     /**
      * Returns the refresh options used for the build.
-     * @deprecated Use #isRefreshDependencies instead.
+     * @deprecated Use {@link #isRefreshDependencies()} instead.
      */
     @Deprecated
     public RefreshOptions getRefreshOptions() {
-        RefreshOptions options = isRefreshDependencies() ? new RefreshOptions(Arrays.asList(RefreshOptions.Option.DEPENDENCIES)) : RefreshOptions.NONE;
-        return options;
+        return isRefreshDependencies() ? new RefreshOptions(Arrays.asList(RefreshOptions.Option.DEPENDENCIES)) : RefreshOptions.NONE;
     }
 
     /**
-     * Specifies whether the depencendies should be refreshed..
+     * Specifies whether the dependencies should be refreshed..
      */
     public boolean isRefreshDependencies() {
         return refreshDependencies;
     }
 
     /**
-     * Specifies whether the depencendies should be refreshed..
+     * Specifies whether the dependencies should be refreshed..
      */
     public void setRefreshDependencies(boolean refreshDependencies) {
         this.refreshDependencies = refreshDependencies;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Action.java b/subprojects/core/src/main/groovy/org/gradle/api/Action.java
deleted file mode 100644
index e8874cd..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/Action.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api;
-
-/**
- * Performs some action against objects of type T.
- *
- * @param <T> The type of object which this action accepts. 
- */
-public interface Action<T> {
-    /**
-     * Performs this action against the given object.
-     *
-     * @param t The object to perform the action on.
-     */
-    void execute(T t);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/JavaVersion.java b/subprojects/core/src/main/groovy/org/gradle/api/JavaVersion.java
deleted file mode 100644
index 9119ba8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/JavaVersion.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api;
-
-import org.gradle.internal.SystemProperties;
-
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-/**
- * An enumeration of Java versions.
- */
-public enum JavaVersion {
-    VERSION_1_1(false), VERSION_1_2(false), VERSION_1_3(false), VERSION_1_4(false), VERSION_1_5(true), VERSION_1_6(true), VERSION_1_7(true);
-
-    private final boolean hasMajorVersion;
-
-    private JavaVersion(boolean hasMajorVersion) {
-        this.hasMajorVersion = hasMajorVersion;
-    }
-
-    /**
-     * Converts the given object into a {@code JavaVersion}.
-     *
-     * @param value An object whose toString() value is to be converted. May be null.
-     * @return The version, or null if the provided value is null.
-     * @throws IllegalArgumentException when the provided value cannot be converted.
-     */
-    public static JavaVersion toVersion(Object value) throws IllegalArgumentException {
-        if (value == null) {
-            return null;
-        }
-        if (value instanceof JavaVersion) {
-            return (JavaVersion) value;
-        }
-
-        String name = value.toString();
-        if (name.matches("\\d")) {
-            int index = Integer.parseInt(name) - 1;
-            if (index < values().length && values()[index].hasMajorVersion) {
-                return values()[index];
-            }
-        }
-
-        Matcher matcher = Pattern.compile("1\\.(\\d)(\\D.*)?").matcher(name);
-        if (matcher.matches()) {
-            return values()[Integer.parseInt(matcher.group(1)) - 1];
-        }
-        throw new IllegalArgumentException(String.format("Could not determine java version from '%s'.", name));
-    }
-
-    /**
-     * Provides the JavaVersion of the current used JVM  {@code JavaVersion}.
-     *
-     * @return The version of the current JVM.
-     */
-    public static JavaVersion current() {
-        return toVersion(SystemProperties.getJavaVersion());
-    }
-
-    @Override
-    public String toString() {
-        return name().substring("VERSION_".length()).replace('_', '.');
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Nullable.java b/subprojects/core/src/main/groovy/org/gradle/api/Nullable.java
deleted file mode 100644
index 7283320..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/Nullable.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Indicates that the value of an element can be null.
- */
- at Documented
- at Retention(RetentionPolicy.RUNTIME)
-public @interface Nullable {}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Project.java b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
index e467507..46aa564 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Project.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
@@ -641,21 +641,21 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      *
      * <ul>
      *
-     * <li>A {@link CharSequence} / {@link String}. Interpreted relative to the project directory, as for {@link #file(Object)}. A string
+     * <li>A {@link CharSequence}, including {@link String} or {@link groovy.lang.GString}. Interpreted relative to the project directory. A string
      * that starts with {@code file:} is treated as a file URL.</li>
      *
-     * <li>{@link File}. If the file is an absolute file, it is returned as is. Otherwise, the file's path is
+     * <li>A {@link File}. If the file is an absolute file, it is returned as is. Otherwise, the file's path is
      * interpreted relative to the project directory.</li>
      *
-     * <li>{@link java.net.URI} or {@link java.net.URL}. The URL's path is interpreted as the file path. Currently, only
+     * <li>A {@link java.net.URI} or {@link java.net.URL}. The URL's path is interpreted as the file path. Currently, only
      * {@code file:} URLs are supported.
      *
-     * <li>{@link Closure}. The closure's return value is resolved recursively.</li>
+     * <li>A {@link Closure}. The closure's return value is resolved recursively.</li>
      *
-     * <li>{@link java.util.concurrent.Callable}. The callable's return value is resolved recursively.</li>
+     * <li>A {@link java.util.concurrent.Callable}. The callable's return value is resolved recursively.</li>
      *
-     * <li>An Object. Its {@code toString()} value is treated the same way as a String, as for {@link #file(Object)}.
-     * This handling of custom Objects has been deprecated and will be removed in the next version of Gradle.</li>
+     * <li>An Object. Its {@code toString()} value is treated the same way as a String.
+     * This is deprecated and will be removed in the next version of Gradle.</li>
      * </ul>
      *
      * @param path The object to resolve as a File.
@@ -697,12 +697,12 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * <p>Returns a {@link ConfigurableFileCollection} containing the given files. You can pass any of the following
      * types to this method:</p>
      *
-     * <ul> <li>A {@link CharSequence} / {@link String}. Interpreted relative to the project directory, as for {@link #file(Object)}. A string
+     * <ul> <li>A {@link CharSequence}, including {@link String} or {@link groovy.lang.GString}. Interpreted relative to the project directory, as for {@link #file(Object)}. A string
      * that starts with {@code file:} is treated as a file URL.</li>
      *
      * <li>A {@link File}. Interpreted relative to the project directory, as for {@link #file(Object)}.</li>
      *
-     * <li>{@link java.net.URI} or {@link java.net.URL}. The URL's path is interpreted as a file path. Currently, only
+     * <li>A {@link java.net.URI} or {@link java.net.URL}. The URL's path is interpreted as a file path. Currently, only
      * {@code file:} URLs are supported.
      *
      * <li>A {@link java.util.Collection}, {@link Iterable}, or an array. May contain any of the types listed here. The elements of the collection
@@ -723,10 +723,10 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * <li>A {@link org.gradle.api.tasks.TaskOutputs}. Converted to the output files the related task.</li>
      *
      * <li>An Object. Its {@code toString()} value is treated the same way as a String, as for {@link #file(Object)}.
-     * Handling custom Objects has been deprecated and will be removed in the next version of Gradle.</li>
+     * This has been deprecated and will be removed in the next version of Gradle.</li>
+     *
+     * </ul>
      *
-     * <li>A Closure. May return any of the types listed here. The return value of the closure is recursively converted
-     * to files. A {@code null} return value is treated as an empty collection.</li></ul>
      * <p>The returned file collection is lazy, so that the paths are evaluated only when the contents of the file
      * collection are queried. The file collection is also live, so that it evaluates the above each time the contents
      * of the collection is queried.</p>
@@ -772,7 +772,6 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
-     * @deprecated Use {@link #fileTree(Object,Closure)} instead.
      * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
      * @return the file tree. Never returns null.
      */
@@ -831,9 +830,11 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
+     * @deprecated Use {@link #fileTree(Object,Closure)} instead.
      * @param closure Closure to configure the {@code ConfigurableFileTree} object
      * @return the configured file tree. Never returns null.
      */
+    @Deprecated
     ConfigurableFileTree fileTree(Closure closure);
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java
index 6969c1b..356be1c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java
@@ -41,5 +41,5 @@ public interface ExternalModuleDependency extends ExternalDependency {
     /**
      * {@inheritDoc}
      */
-    ExternalModuleDependency copy();    
+    ExternalModuleDependency copy();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java
index ecc6a8e..fc23ef9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/LenientConfiguration.java
@@ -27,7 +27,7 @@ import java.util.Set;
 public interface LenientConfiguration {
 
     /**
-     * returns successfully resolved dependencies
+     * Returns successfully resolved dependencies.
      *
      * @param dependencySpec dependency spec
      * @return only resolved dependencies
@@ -43,11 +43,20 @@ public interface LenientConfiguration {
     public Set<UnresolvedDependency> getUnresolvedModuleDependencies();
 
     /**
-     * returns successfully resolved files for successfully resolved dependencies
+     * Returns successfully resolved files for successfully resolved dependencies.
      *
      * @param dependencySpec dependency spec
      * @return resolved dependencies files
      */
     public Set<File> getFiles(Spec<? super Dependency> dependencySpec);
 
+    /**
+     * Gets successfully resolved artifacts for dependencies that match given dependency spec.
+     *
+     * TODO SF We need to pair and model the successfully and unsuccessfully resolved artifacts much better.
+     *
+     * @param dependencySpec dependency spec
+     * @return successfully resolved artifacts for dependencies that match given dependency spec
+     */
+    Set<ResolvedArtifact> getArtifacts(Spec<? super Dependency> dependencySpec);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnresolvedDependency.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnresolvedDependency.java
index 17d5dc9..5865857 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnresolvedDependency.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/UnresolvedDependency.java
@@ -21,11 +21,21 @@ package org.gradle.api.artifacts;
 public interface UnresolvedDependency {
 
     /**
+     * Deprecated. Please use {@link #getSelector()}
+     * <p>
      * Returns the identifier of the dependency, for example group:name:version
      */
+    @Deprecated
     String getId();
 
     /**
+     * The module selector of the dependency.
+     *
+     * @since 1.1-rc-1
+     */
+    ModuleVersionSelector getSelector();
+
+    /**
      * the exception that is the cause of unresolved state
      */
     Throwable getProblem();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java
index 8b6b555..b86894a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ArtifactResolutionControl.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.artifacts.cache;
 
+import org.gradle.api.Experimental;
 import org.gradle.api.artifacts.ArtifactIdentifier;
 
 import java.io.File;
@@ -22,5 +23,6 @@ import java.io.File;
 /**
  * Command methods for controlling artifact resolution via the DSL.
  */
+ at Experimental
 public interface ArtifactResolutionControl extends ResolutionControl<ArtifactIdentifier, File> {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/DependencyResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/DependencyResolutionControl.java
index 4b3374b..ce616ac 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/DependencyResolutionControl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/DependencyResolutionControl.java
@@ -15,11 +15,13 @@
  */
 package org.gradle.api.artifacts.cache;
 
+import org.gradle.api.Experimental;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 
 /**
  * Command methods for controlling dependency resolution via the DSL.
  */
+ at Experimental
 public interface DependencyResolutionControl extends ResolutionControl<ModuleVersionSelector, ModuleVersionIdentifier> {
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ModuleResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ModuleResolutionControl.java
index ab2aec2..4cefe6b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ModuleResolutionControl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ModuleResolutionControl.java
@@ -15,12 +15,14 @@
  */
 package org.gradle.api.artifacts.cache;
 
+import org.gradle.api.Experimental;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ResolvedModuleVersion;
 
 /**
  * Command methods for controlling module resolution via the DSL.
  */
+ at Experimental
 public interface ModuleResolutionControl extends ResolutionControl<ModuleVersionIdentifier, ResolvedModuleVersion> {
     // TODO: This should be part of the cached result?
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionControl.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionControl.java
index f95d305..fa1a212 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionControl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionControl.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.api.artifacts.cache;
 
+import org.gradle.api.Experimental;
+
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -22,6 +24,7 @@ import java.util.concurrent.TimeUnit;
  * @param <A> The type of the request object for this resolution
  * @param <B> The type of the result of this resolution
  */
+ at Experimental
 public interface ResolutionControl<A, B> {
     /**
      * Returns the query object that was requested in this resolution.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionRules.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionRules.java
index 56f88e0..ba3ba96 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionRules.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/cache/ResolutionRules.java
@@ -16,11 +16,13 @@
 package org.gradle.api.artifacts.cache;
 
 import org.gradle.api.Action;
+import org.gradle.api.Experimental;
 
 /**
  * Represents a set of rules/actions that can be applied during dependency resolution.
  * Currently these are restricted to controlling caching, but these could possibly be extended in the future to include other manipulations.
  */
+ at Experimental
 public interface ResolutionRules {
     /**
      * Apply a rule to control resolution of dependencies.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/RelativePath.java b/subprojects/core/src/main/groovy/org/gradle/api/file/RelativePath.java
index 982b2a3..43632b4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/RelativePath.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/RelativePath.java
@@ -1,199 +1,200 @@
-/*
- * Copyright 2010 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 org.gradle.api.file;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.ListIterator;
-
-/**
- * <p>Represents a relative path from some base directory to a file.  Used in file copying to represent both a source
- * and target file path when copying files.</p>
- *
- * <p>{@code RelativePath} instances are immutable.</p>
- *
- * @author Steve Appling
- */
-public class RelativePath {
-    private final boolean endsWithFile;
-    private final String[] segments;
-
-    /**
-     * Creates a {@code RelativePath}.
-     *
-     * @param endsWithFile - if true, the path ends with a file, otherwise a directory
-     */
-    public RelativePath(boolean endsWithFile, String... segments) {
-        this(endsWithFile, null, segments);
-    }
-
-    private RelativePath(boolean endsWithFile, RelativePath parentPath, String... childSegments) {
-        this.endsWithFile = endsWithFile;
-        int sourceLength = 0;
-        if (parentPath != null) {
-            String[] sourceSegments = parentPath.getSegments();
-            sourceLength = sourceSegments.length;
-            segments = new String[sourceLength + childSegments.length];
-            System.arraycopy(sourceSegments, 0, segments, 0, sourceLength);
-        } else {
-            segments = new String[childSegments.length];
-        }
-        System.arraycopy(childSegments, 0, segments, sourceLength, childSegments.length);
-    }
-
-    public String[] getSegments() {
-        return segments;
-    }
-
-    public ListIterator<String> segmentIterator() {
-        ArrayList<String> content = new ArrayList<String>(Arrays.asList(segments));
-        return content.listIterator();
-    }
-
-    public boolean isFile() {
-        return endsWithFile;
-    }
-
-    public String getPathString() {
-        return GUtil.join(segments, "/");
-    }
-
-    public File getFile(File baseDir) {
-        return new File(baseDir, getPathString());
-    }
-
-    public String getLastName() {
-        if (segments.length > 0) {
-            return segments[segments.length - 1];
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        RelativePath that = (RelativePath) o;
-
-        if (endsWithFile != that.endsWithFile) {
-            return false;
-        }
-        if (!Arrays.equals(segments, that.segments)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = endsWithFile ? 1 : 0;
-        result = 31 * result + Arrays.hashCode(segments);
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return getPathString();
-    }
-
-    /**
-     * Returns the parent of this path.
-     *
-     * @return The parent of this path, or null if this is the root path.
-     */
-    public RelativePath getParent() {
-        if (segments.length == 0) {
-            return null;
-        }
-        String[] parentSegments = new String[segments.length - 1];
-        System.arraycopy(segments, 0, parentSegments, 0, parentSegments.length);
-        return new RelativePath(false, parentSegments);
-    }
-
-    public static RelativePath parse(boolean isFile, String path) {
-        return parse(isFile, null, path);
-    }
-
-    public static RelativePath parse(boolean isFile, RelativePath parent, String path) {
-        String[] names = StringUtils.split(path, "/" + File.separator);
-        return new RelativePath(isFile, parent, names);
-    }
-
-    /**
-     * <p>Returns a copy of this path, with the last name replaced with the given name.</p>
-     *
-     * @param name The name.
-     * @return The path.
-     */
-    public RelativePath replaceLastName(String name) {
-        String[] newSegments = new String[segments.length];
-        System.arraycopy(segments, 0, newSegments, 0, segments.length);
-        newSegments[segments.length - 1] = name;
-        return new RelativePath(endsWithFile, newSegments);
-    }
-
-    /**
-     * <p>Appends the given path to the end of this path.
-     *
-     * @param other The path to append
-     * @return The new path
-     */
-    public RelativePath append(RelativePath other) {
-        return new RelativePath(other.endsWithFile, this, other.segments);
-    }
-
-    /**
-     * <p>Appends the given path to the end of this path.
-     *
-     * @param other The path to append
-     * @return The new path
-     */
-    public RelativePath plus(RelativePath other) {
-        return append(other);
-    }
-
-    /**
-     * Appends the given names to the end of this path.
-     *
-     * @param segments The names to append.
-     * @param endsWithFile when true, the new path refers to a file.
-     * @return The new path.
-     */
-    public RelativePath append(boolean endsWithFile, String... segments) {
-        return new RelativePath(endsWithFile, this, segments);
-    }
-
-    /**
-     * Prepends the given names to the start of this path.
-     *
-     * @param segments The names to prepend
-     * @return The new path.
-     */
-    public RelativePath prepend(String... segments) {
-        return new RelativePath(false, segments).append(this);
-    }
-}
+/*
+ * Copyright 2010 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 org.gradle.api.file;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.ListIterator;
+
+/**
+ * <p>Represents a relative path from some base directory to a file.  Used in file copying to represent both a source
+ * and target file path when copying files.</p>
+ *
+ * <p>{@code RelativePath} instances are immutable.</p>
+ *
+ * @author Steve Appling
+ */
+public class RelativePath implements Serializable {
+    private final boolean endsWithFile;
+    private final String[] segments;
+
+    /**
+     * Creates a {@code RelativePath}.
+     *
+     * @param endsWithFile - if true, the path ends with a file, otherwise a directory
+     */
+    public RelativePath(boolean endsWithFile, String... segments) {
+        this(endsWithFile, null, segments);
+    }
+
+    private RelativePath(boolean endsWithFile, RelativePath parentPath, String... childSegments) {
+        this.endsWithFile = endsWithFile;
+        int sourceLength = 0;
+        if (parentPath != null) {
+            String[] sourceSegments = parentPath.getSegments();
+            sourceLength = sourceSegments.length;
+            segments = new String[sourceLength + childSegments.length];
+            System.arraycopy(sourceSegments, 0, segments, 0, sourceLength);
+        } else {
+            segments = new String[childSegments.length];
+        }
+        System.arraycopy(childSegments, 0, segments, sourceLength, childSegments.length);
+    }
+
+    public String[] getSegments() {
+        return segments;
+    }
+
+    public ListIterator<String> segmentIterator() {
+        ArrayList<String> content = new ArrayList<String>(Arrays.asList(segments));
+        return content.listIterator();
+    }
+
+    public boolean isFile() {
+        return endsWithFile;
+    }
+
+    public String getPathString() {
+        return GUtil.join(segments, "/");
+    }
+
+    public File getFile(File baseDir) {
+        return new File(baseDir, getPathString());
+    }
+
+    public String getLastName() {
+        if (segments.length > 0) {
+            return segments[segments.length - 1];
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        RelativePath that = (RelativePath) o;
+
+        if (endsWithFile != that.endsWithFile) {
+            return false;
+        }
+        if (!Arrays.equals(segments, that.segments)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = endsWithFile ? 1 : 0;
+        result = 31 * result + Arrays.hashCode(segments);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return getPathString();
+    }
+
+    /**
+     * Returns the parent of this path.
+     *
+     * @return The parent of this path, or null if this is the root path.
+     */
+    public RelativePath getParent() {
+        if (segments.length == 0) {
+            return null;
+        }
+        String[] parentSegments = new String[segments.length - 1];
+        System.arraycopy(segments, 0, parentSegments, 0, parentSegments.length);
+        return new RelativePath(false, parentSegments);
+    }
+
+    public static RelativePath parse(boolean isFile, String path) {
+        return parse(isFile, null, path);
+    }
+
+    public static RelativePath parse(boolean isFile, RelativePath parent, String path) {
+        String[] names = StringUtils.split(path, "/" + File.separator);
+        return new RelativePath(isFile, parent, names);
+    }
+
+    /**
+     * <p>Returns a copy of this path, with the last name replaced with the given name.</p>
+     *
+     * @param name The name.
+     * @return The path.
+     */
+    public RelativePath replaceLastName(String name) {
+        String[] newSegments = new String[segments.length];
+        System.arraycopy(segments, 0, newSegments, 0, segments.length);
+        newSegments[segments.length - 1] = name;
+        return new RelativePath(endsWithFile, newSegments);
+    }
+
+    /**
+     * <p>Appends the given path to the end of this path.
+     *
+     * @param other The path to append
+     * @return The new path
+     */
+    public RelativePath append(RelativePath other) {
+        return new RelativePath(other.endsWithFile, this, other.segments);
+    }
+
+    /**
+     * <p>Appends the given path to the end of this path.
+     *
+     * @param other The path to append
+     * @return The new path
+     */
+    public RelativePath plus(RelativePath other) {
+        return append(other);
+    }
+
+    /**
+     * Appends the given names to the end of this path.
+     *
+     * @param segments The names to append.
+     * @param endsWithFile when true, the new path refers to a file.
+     * @return The new path.
+     */
+    public RelativePath append(boolean endsWithFile, String... segments) {
+        return new RelativePath(endsWithFile, this, segments);
+    }
+
+    /**
+     * Prepends the given names to the start of this path.
+     *
+     * @param segments The names to prepend
+     * @return The new path.
+     */
+    public RelativePath prepend(String... segments) {
+        return new RelativePath(false, segments).append(this);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/UnableToDeleteFileException.java b/subprojects/core/src/main/groovy/org/gradle/api/file/UnableToDeleteFileException.java
new file mode 100644
index 0000000..32239c6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/UnableToDeleteFileException.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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 org.gradle.api.file;
+
+import org.gradle.api.UncheckedIOException;
+
+import java.io.File;
+
+/**
+ * Thrown by Gradle when it is unable to delete a file.
+ */
+public class UnableToDeleteFileException extends UncheckedIOException {
+
+    private final File file;
+
+    public UnableToDeleteFileException(File file) {
+        super(toMessage(file));
+        this.file = file;
+
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    private static String toMessage(File file) {
+        return String.format("Unable to delete %s: %s", file.isDirectory() ? "directory" : "file", file.getAbsolutePath());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/initialization/Settings.java b/subprojects/core/src/main/groovy/org/gradle/api/initialization/Settings.java
index 7401245..ad79d28 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/initialization/Settings.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/initialization/Settings.java
@@ -39,7 +39,7 @@ import java.io.File;
  * directory defaults to the directory containing the settings file.</p>
  *
  * <p>When a project is included in the build, a {@link ProjectDescriptor} is created. You can use this descriptor to
- * change the default vaules for several properties of the project.</p>
+ * change the default values for several properties of the project.</p>
  *
  * <h3>Using Settings in a Settings File</h3>
  *
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java
index a4f96be..a0b79dd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java
@@ -22,6 +22,8 @@ import groovy.lang.*;
 import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Modifier;
@@ -60,22 +62,19 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
             ClassBuilder<T> builder = start(type);
 
             boolean isConventionAware = type.getAnnotation(NoConventionMapping.class) == null;
-            boolean isDynamicAware = type.getAnnotation(NoDynamicObject.class) == null;
 
-            builder.startClass(isConventionAware, isDynamicAware);
+            builder.startClass(isConventionAware);
 
-            if (isDynamicAware && !DynamicObjectAware.class.isAssignableFrom(type)) {
+            if (!DynamicObjectAware.class.isAssignableFrom(type)) {
                 if (ExtensionAware.class.isAssignableFrom(type)) {
                     throw new UnsupportedOperationException("A type that implements ExtensionAware must currently also implement DynamicObjectAware.");
                 }
                 builder.mixInDynamicAware();
             }
-            if (isDynamicAware && !GroovyObject.class.isAssignableFrom(type)) {
+            if (!GroovyObject.class.isAssignableFrom(type)) {
                 builder.mixInGroovyObject();
             }
-            if (isDynamicAware) {
-                builder.addDynamicMethods();
-            }
+            builder.addDynamicMethods();
             if (isConventionAware && !IConventionAware.class.isAssignableFrom(type)) {
                 builder.mixInConventionAware();
             }
@@ -196,7 +195,7 @@ public abstract class AbstractClassGenerator implements ClassGenerator {
     protected abstract <T> ClassBuilder<T> start(Class<T> type);
 
     protected interface ClassBuilder<T> {
-        void startClass(boolean isConventionAware, boolean isDynamicAware);
+        void startClass(boolean isConventionAware);
 
         void addConstructor(Constructor<?> constructor) throws Exception;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
index 0879440..327d636 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
@@ -19,6 +19,7 @@ import groovy.lang.Closure;
 import org.gradle.api.Named;
 import org.gradle.api.NamedDomainObjectContainer;
 import org.gradle.api.Namer;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 
 public abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamedDomainObjectSet<T> implements NamedDomainObjectContainer<T> {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
index 368468d..d591fa1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
@@ -39,11 +39,11 @@ import org.gradle.api.tasks.TaskInputs;
 import org.gradle.api.tasks.TaskInstantiationException;
 import org.gradle.api.tasks.TaskState;
 import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.StandardOutputCapture;
 import org.gradle.util.ConfigureUtil;
-import org.gradle.util.DeprecationLogger;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -301,11 +301,6 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
         return loggingManager;
     }
 
-    public DynamicObjectHelper getDynamicObjectHelper() {
-        DeprecationLogger.nagUserOfReplacedMethod("AbstractTask.getDynamicObjectHelper()", "getAsDynamicObject()");
-        return new DynamicObjectHelper(extensibleDynamicObject);
-    }
-
     public Object property(String propertyName) throws MissingPropertyException {
         return extensibleDynamicObject.getProperty(propertyName);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
index bec7f16..c35cbbb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
@@ -17,9 +17,9 @@ package org.gradle.api.internal;
 
 import groovy.lang.*;
 import org.gradle.api.Action;
-import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.internal.reflect.JavaReflectionUtil;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.ReflectionUtil;
 import org.objectweb.asm.*;
@@ -51,8 +51,6 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
         private final Type dynamicObjectType = Type.getType(DynamicObject.class);
         private final Type conventionMappingType = Type.getType(ConventionMapping.class);
         private final Type groovyObjectType = Type.getType(GroovyObject.class);
-        private boolean dynamicAware;
-        private final Type defaultConventionType = Type.getType(DefaultConvention.class);
         private final Type conventionType = Type.getType(Convention.class);
 
         private ClassBuilderImpl(Class<T> type) {
@@ -64,18 +62,15 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             superclassType = Type.getType(type);
         }
 
-        public void startClass(boolean isConventionAware, boolean isDynamicAware) {
-            dynamicAware = isDynamicAware;
+        public void startClass(boolean isConventionAware) {
             List<String> interfaceTypes = new ArrayList<String>();
             if (isConventionAware) {
                 interfaceTypes.add(conventionAwareType.getInternalName());
             }
-            if (isDynamicAware) {
-                interfaceTypes.add(dynamicObjectAwareType.getInternalName());
-                interfaceTypes.add(extensionAwareType.getInternalName());
-                interfaceTypes.add(hasConventionType.getInternalName());
-                interfaceTypes.add(groovyObjectType.getInternalName());
-            }
+            interfaceTypes.add(dynamicObjectAwareType.getInternalName());
+            interfaceTypes.add(extensionAwareType.getInternalName());
+            interfaceTypes.add(hasConventionType.getInternalName());
+            interfaceTypes.add(groovyObjectType.getInternalName());
 
             visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null,
                     superclassType.getInternalName(), interfaceTypes.toArray(new String[interfaceTypes.size()]));
@@ -233,24 +228,13 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
                     visitor.visitInsn(Opcodes.DUP);
                     visitor.visitVarInsn(Opcodes.ALOAD, 0);
 
-                    if (dynamicAware) {
-                        // GENERATE getConvention()
-
-                        visitor.visitVarInsn(Opcodes.ALOAD, 0);
-                        visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(), "getConvention",
-                                getConventionDesc);
-
-                        // END
-                    } else {
-                        // GENERATE new DefaultConvention()
+                    // GENERATE getConvention()
 
-                        visitor.visitTypeInsn(Opcodes.NEW, defaultConventionType.getInternalName());
-                        visitor.visitInsn(Opcodes.DUP);
-                        visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, defaultConventionType.getInternalName(),
-                                "<init>", "()V");
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(), "getConvention",
+                            getConventionDesc);
 
-                        // END
-                    }
+                    // END
 
                     String constructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
                             conventionAwareType, conventionType
@@ -549,7 +533,7 @@ public class AsmBackedClassGenerator extends AbstractClassGenerator {
             Type boxedType = null;
             if (getter.getReturnType().isPrimitive()) {
                 // Box value
-                boxedType = Type.getType(ReflectionUtil.getWrapperTypeForPrimitiveType(getter.getReturnType()));
+                boxedType = Type.getType(JavaReflectionUtil.getWrapperTypeForPrimitiveType(getter.getReturnType()));
                 String valueOfMethodDescriptor = Type.getMethodDescriptor(boxedType, new Type[]{returnType});
                 methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, boxedType.getInternalName(), "valueOf", valueOfMethodDescriptor);
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java
index cba14d1..2a28305 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiator.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.api.internal;
 
+import org.gradle.internal.reflect.Instantiator;
+
 public class ClassGeneratorBackedInstantiator implements Instantiator {
     private final ClassGenerator classGenerator;
     private final Instantiator instantiator;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java
index 14eaedf..6302445 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal;
 
-import org.gradle.util.ClassPath;
+import org.gradle.internal.classpath.ClassPath;
 
 public interface ClassPathProvider {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java
index 99cbed6..c9bd7fb 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal;
 
-import org.gradle.util.ClassPath;
+import org.gradle.internal.classpath.ClassPath;
 
 public interface ClassPathRegistry {
     ClassPath getClassPath(String name);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java
index e783987..3012683 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java
@@ -17,8 +17,8 @@ package org.gradle.api.internal;
 
 import org.gradle.api.internal.classpath.Module;
 import org.gradle.api.internal.classpath.ModuleRegistry;
-import org.gradle.util.ClassPath;
-import org.gradle.util.DefaultClassPath;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 
 public class DefaultClassPathProvider implements ClassPathProvider {
     private final ModuleRegistry moduleRegistry;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java
index 4d37c3d..86ae946 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal;
 
-import org.gradle.util.ClassPath;
+import org.gradle.internal.classpath.ClassPath;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java
index 273c635..5d6dfbc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectCollection.java
@@ -25,6 +25,7 @@ import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.ExtensionContainer;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 
 import java.util.*;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectList.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectList.java
index f96ecc0..7eac48a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectList.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectList.java
@@ -23,6 +23,7 @@ import org.gradle.api.internal.collections.CollectionFilter;
 import org.gradle.api.internal.collections.FilteredList;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.util.*;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSet.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSet.java
index 008f1e9..34e7307 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSet.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSet.java
@@ -24,6 +24,7 @@ import org.gradle.api.internal.collections.CollectionFilter;
 import org.gradle.api.internal.collections.FilteredSet;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyClassPathProvider.java
index ca7f7fa..5423975 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DependencyClassPathProvider.java
@@ -20,8 +20,8 @@ import org.gradle.api.internal.classpath.Module;
 import org.gradle.api.internal.classpath.ModuleRegistry;
 import org.gradle.api.internal.classpath.PluginModuleRegistry;
 import org.gradle.api.internal.classpath.UnknownModuleException;
-import org.gradle.util.ClassPath;
-import org.gradle.util.DefaultClassPath;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 
 import static org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory.ClassPathNotation.GRADLE_API;
 import static org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory.ClassPathNotation.LOCAL_GROOVY;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectInstantiator.java
deleted file mode 100644
index 6f7b4dc..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DirectInstantiator.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.util.ReflectionUtil;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.*;
-
-public class DirectInstantiator implements Instantiator {
-    public <T> T newInstance(Class<T> type, Object... params) {
-        try {
-            List<Constructor<?>> matches = new ArrayList<Constructor<?>>();
-            for (Constructor<?> constructor : type.getConstructors()) {
-                if (isMatch(constructor, params)) {
-                    matches.add(constructor);
-                }
-            }
-            if (matches.isEmpty()) {
-                throw new IllegalArgumentException(String.format("Could not find any public constructor for %s which accepts parameters %s.", type, Arrays.toString(params)));
-            }
-            if (matches.size() > 1) {
-                throw new IllegalArgumentException(String.format("Found multiple public constructors for %s which accept parameters %s.", type, Arrays.toString(params)));
-            }
-            return type.cast(matches.get(0).newInstance(params));
-        } catch (InvocationTargetException e) {
-            throw UncheckedException.throwAsUncheckedException(e.getCause());
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    private boolean isMatch(Constructor<?> constructor, Object... params) {
-        if (constructor.getParameterTypes().length != params.length) {
-            return false;
-        }
-        for (int i = 0; i < params.length; i++) {
-            Object param = params[i];
-            Class<?> parameterType = constructor.getParameterTypes()[i];
-            if (parameterType.isPrimitive()) {
-                if (!ReflectionUtil.getWrapperTypeForPrimitiveType(parameterType).isInstance(param)) {
-                    return false;
-                }
-            } else {
-                if (param != null && !parameterType.isInstance(param)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicModulesClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicModulesClassPathProvider.java
index 83cec61..dfca66a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicModulesClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicModulesClassPathProvider.java
@@ -18,8 +18,8 @@ package org.gradle.api.internal;
 import org.gradle.api.internal.classpath.Module;
 import org.gradle.api.internal.classpath.ModuleRegistry;
 import org.gradle.api.internal.classpath.PluginModuleRegistry;
-import org.gradle.util.ClassPath;
-import org.gradle.util.DefaultClassPath;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 
 public class DynamicModulesClassPathProvider implements ClassPathProvider {
     private final ModuleRegistry moduleRegistry;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java
index 3bb87f7..5f02139 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java
@@ -16,18 +16,37 @@
 
 package org.gradle.api.internal;
 
+import groovy.lang.*;
+import groovy.lang.MissingMethodException;
+
+import java.util.Map;
+
 /**
- * This is necessary because DynamicObjectHelper was renamed to ExtensibleDynamicObject in 1.0-milestone-9.
- *
- * AbstractTask leaked DynamicObjectHelper by having a public method that returned this type. This method
- * has been deprecated but we need to keep a class around with the same name for backwards compatibility.
- *
- * This will probably have to stay until we remove task inheritance.
+ * @deprecated This is here because tasks implemented in Groovy that are compiled against older versions of Gradle have this type baked into their byte-code, and cannot be loaded if it's not found.
  */
-public class DynamicObjectHelper extends BeanDynamicObject {
+ at Deprecated
+public class DynamicObjectHelper implements DynamicObject {
+    public Map<String, ?> getProperties() {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean hasProperty(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object getProperty(String name) throws MissingPropertyException {
+        throw new UnsupportedOperationException();
+    }
 
-    public DynamicObjectHelper(ExtensibleDynamicObject delegate) {
-        super(delegate);
+    public void setProperty(String name, Object value) throws MissingPropertyException {
+        throw new UnsupportedOperationException();
     }
 
+    public boolean hasMethod(String name, Object... arguments) {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java
index 66af052..8549ddf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ExtensibleDynamicObject.java
@@ -20,6 +20,7 @@ import org.gradle.api.internal.plugins.DefaultConvention;
 import org.gradle.api.internal.plugins.ExtraPropertiesDynamicObjectAdapter;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.ExtraPropertiesExtension;
+import org.gradle.internal.reflect.Instantiator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -38,7 +39,6 @@ public class ExtensibleDynamicObject extends CompositeDynamicObject implements H
         BeforeConvention, AfterConvention
     }
 
-    private final Object delegate;
     private final AbstractDynamicObject dynamicDelegate;
     private DynamicObject parent;
     private Convention convention;
@@ -67,7 +67,6 @@ public class ExtensibleDynamicObject extends CompositeDynamicObject implements H
     }
 
     public ExtensibleDynamicObject(Object delegate, AbstractDynamicObject dynamicDelegate, Convention convention) {
-        this.delegate = delegate;
         this.dynamicDelegate = dynamicDelegate;
         this.convention = convention;
         this.extraPropertiesDynamicObject = new ExtraPropertiesDynamicObjectAdapter(delegate, this, convention.getExtraProperties());
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainer.java
index 095d516..50fec61 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainer.java
@@ -19,6 +19,7 @@ import groovy.lang.Closure;
 import org.gradle.api.Namer;
 import org.gradle.api.Named;
 import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.internal.reflect.Instantiator;
 
 public class FactoryNamedDomainObjectContainer<T> extends AbstractNamedDomainObjectContainer<T> {
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/Instantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/Instantiator.java
deleted file mode 100644
index ee91ad7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/Instantiator.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal;
-
-/**
- * An object that can create new instances of a given type, which may be decorated in some fashion.
- */
-public interface Instantiator {
-
-    /**
-     * Create a new instance of T, using {@code parameters} as the construction parameters.
-     */
-    <T> T newInstance(Class<T> type, Object... parameters);
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/NoDynamicObject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/NoDynamicObject.java
deleted file mode 100644
index e5dd17e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/NoDynamicObject.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api.internal;
-
-import java.lang.annotation.*;
-
-/**
- * Disables the application of dynamic object behaviour for the class it is attached to.
- *
- * @see DynamicObjectAware
- */
- at Target(ElementType.TYPE)
- at Retention(RetentionPolicy.RUNTIME)
- at Inherited
-public @interface NoDynamicObject {
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/Operation.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/Operation.java
deleted file mode 100644
index 5caafa6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/Operation.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal;
-
-/**
- * Generic, parameter-less and void returning operation of some kind.
- * <p>
- * by Szczepan Faber, created at: 12/16/11
- */
-public interface Operation {
-    void execute();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ReflectiveNamedDomainObjectFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ReflectiveNamedDomainObjectFactory.java
index c956d78..8db6a20 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ReflectiveNamedDomainObjectFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ReflectiveNamedDomainObjectFactory.java
@@ -16,6 +16,8 @@
 package org.gradle.api.internal;
 
 import org.gradle.api.NamedDomainObjectFactory;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
 
 public class ReflectiveNamedDomainObjectFactory<T> implements NamedDomainObjectFactory<T> {
     private final Class<? extends T> type;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ThreadGlobalInstantiator.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ThreadGlobalInstantiator.java
index c6f9f2c..b9653a9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ThreadGlobalInstantiator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ThreadGlobalInstantiator.java
@@ -16,6 +16,9 @@
 
 package org.gradle.api.internal;
 
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
+
 import java.util.Stack;
 
 /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
index 608e634..0a1b6b1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
@@ -26,7 +26,7 @@ import org.gradle.api.artifacts.repositories.ArtifactRepository;
 import org.gradle.api.artifacts.ArtifactRepositoryContainer;
 import org.gradle.api.artifacts.UnknownRepositoryException;
 import org.gradle.api.internal.DefaultNamedDomainObjectList;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
 import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository;
 import org.gradle.util.ConfigureUtil;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
index 1e6da83..197cc84 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolutionServices.java
@@ -31,4 +31,6 @@ public interface DependencyResolutionServices {
     ArtifactHandler getArtifactHandler();
 
     Factory<ArtifactPublicationServices> getPublishServicesFactory();
+
+    ResolverFactory getResolverFactory();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
index 90d1543..b49651f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
@@ -23,7 +23,7 @@ import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.UnknownConfigurationException;
 import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
 import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.listener.ListenerManager;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/AbstractScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/AbstractScriptTransformer.java
deleted file mode 100644
index 55595c4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/AbstractScriptTransformer.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api.internal.artifacts.dsl;
-
-import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.GroovyCodeVisitor;
-import org.codehaus.groovy.ast.MethodNode;
-import org.codehaus.groovy.control.CompilationUnit;
-import org.codehaus.groovy.ast.expr.*;
-import org.codehaus.groovy.control.SourceUnit;
-import org.gradle.groovy.scripts.Transformer;
-
-public abstract class AbstractScriptTransformer extends CompilationUnit.SourceUnitOperation implements Transformer {
-    public void register(CompilationUnit compilationUnit) {
-        compilationUnit.addPhaseOperation(this, getPhase());
-    }
-
-    protected abstract int getPhase();
-
-    protected boolean isMethodOnThis(MethodCallExpression call, String name) {
-        boolean isTaskMethod = call.getMethod() instanceof ConstantExpression && call.getMethod().getText().equals(
-                name);
-        return isTaskMethod && targetIsThis(call);
-    }
-
-    protected boolean targetIsThis(MethodCallExpression call) {
-        Expression target = call.getObjectExpression();
-        return target instanceof VariableExpression && target.getText().equals("this");
-    }
-
-    protected void visitScriptCode(SourceUnit source, GroovyCodeVisitor transformer) {
-        source.getAST().getStatementBlock().visit(transformer);
-        for (Object method : source.getAST().getMethods()) {
-            MethodNode methodNode = (MethodNode) method;
-            methodNode.getCode().visit(transformer);
-        }
-    }
-
-    protected ClassNode getScriptClass(SourceUnit source) {
-        if (source.getAST().getStatementBlock().getStatements().isEmpty() && source.getAST().getMethods().isEmpty()) {
-            // There is no script class when there are no statements or methods declared in the script
-            return null;
-        }
-        return source.getAST().getClasses().get(0);
-    }
-
-    protected void removeMethod(ClassNode declaringClass, MethodNode methodNode) {
-        declaringClass.getMethods().remove(methodNode);
-        declaringClass.getDeclaredMethods(methodNode.getName()).clear();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptClasspathScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptClasspathScriptTransformer.java
deleted file mode 100644
index d9c7885..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptClasspathScriptTransformer.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api.internal.artifacts.dsl;
-
-/**
- * An implementation of ClasspathScriptTransformer for use in build scripts.  This subclass defines the script method
- * name to be buildscript {}.
- */
-public class BuildScriptClasspathScriptTransformer extends ClasspathScriptTransformer {
-    private final String classpathClosureName;
-
-    public BuildScriptClasspathScriptTransformer(String classpathClosureName) {
-        this.classpathClosureName = classpathClosureName;
-    }
-
-    public String getId() {
-        return classpathClosureName;
-    }
-
-    protected String getScriptMethodName() {
-        return classpathClosureName;
-    }
-}
-
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptTransformer.java
deleted file mode 100644
index f4a3031..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptTransformer.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api.internal.artifacts.dsl;
-
-import org.codehaus.groovy.control.CompilationUnit;
-import org.gradle.groovy.scripts.Transformer;
-
-public class BuildScriptTransformer implements Transformer {
-    private final BuildScriptClasspathScriptTransformer classpathScriptTransformer;
-
-    public BuildScriptTransformer(BuildScriptClasspathScriptTransformer transformer) {
-        classpathScriptTransformer = transformer;
-    }
-
-    public String getId() {
-        return "no_" + classpathScriptTransformer.getId();
-    }
-
-    public void register(CompilationUnit compilationUnit) {
-        classpathScriptTransformer.invert().register(compilationUnit);
-        TaskDefinitionScriptTransformer taskDefinitionScriptTransformer = new TaskDefinitionScriptTransformer();
-        taskDefinitionScriptTransformer.register(compilationUnit);
-        // TODO - remove this
-        FixMainScriptTransformer fixMainScriptTransformer = new FixMainScriptTransformer();
-        fixMainScriptTransformer.register(compilationUnit);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java
deleted file mode 100644
index 874d011..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api.internal.artifacts.dsl;
-
-import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.ImportNode;
-import org.codehaus.groovy.ast.MethodNode;
-import org.codehaus.groovy.ast.ModuleNode;
-import org.codehaus.groovy.ast.expr.ArgumentListExpression;
-import org.codehaus.groovy.ast.expr.ClosureExpression;
-import org.codehaus.groovy.ast.expr.MethodCallExpression;
-import org.codehaus.groovy.ast.stmt.ExpressionStatement;
-import org.codehaus.groovy.ast.stmt.Statement;
-import org.codehaus.groovy.control.CompilationFailedException;
-import org.codehaus.groovy.control.Phases;
-import org.codehaus.groovy.control.SourceUnit;
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.groovy.scripts.Transformer;
-import org.gradle.internal.UncheckedException;
-
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * The classpath script transformer uses Groovy's AST support to implement a two-phase
- * compilation of a script into a "class path script" and an "everything else script".
- * The classpath script can then be executed and it's results taken into account (in
- * particular, to update the classpath) before the remainder of the script is executed.
- */
-public abstract class ClasspathScriptTransformer extends AbstractScriptTransformer {
-    protected abstract String getScriptMethodName();
-
-    protected int getPhase() {
-        return Phases.CONVERSION;
-    }
-
-    public void call(SourceUnit source) throws CompilationFailedException {
-        Spec<Statement> spec = isScriptBlock();
-        filterStatements(source, spec);
-
-        // Filter imported classes which are not available yet
-
-        Iterator<ImportNode> iter = source.getAST().getImports().iterator();
-        while (iter.hasNext()) {
-            ImportNode importedClass = iter.next();
-            if (!isVisible(source, importedClass.getClassName())) {
-                try {
-                    Field field = ModuleNode.class.getDeclaredField("imports");
-                    field.setAccessible(true);
-                    Map value = (Map) field.get(source.getAST());
-                    value.remove(importedClass.getAlias());
-                } catch (Exception e) {
-                    throw UncheckedException.throwAsUncheckedException(e);
-                }
-            }
-        }
-
-        iter = source.getAST().getStaticImports().values().iterator();
-        while (iter.hasNext()) {
-            ImportNode importedClass = iter.next();
-            if (!isVisible(source, importedClass.getClassName())) {
-                iter.remove();
-            }
-        }
-
-        iter = source.getAST().getStaticStarImports().values().iterator();
-        while (iter.hasNext()) {
-            ImportNode importedClass = iter.next();
-            if (!isVisible(source, importedClass.getClassName())) {
-                iter.remove();
-            }
-        }
-
-        ClassNode scriptClass = getScriptClass(source);
-
-        // Remove all the classes other than the main class
-        Iterator<ClassNode> classes = source.getAST().getClasses().iterator();
-        while (classes.hasNext()) {
-            ClassNode classNode = classes.next();
-            if (classNode != scriptClass) {
-                classes.remove();
-            }
-        }
-
-        // Remove all the methods from the main class
-        if (scriptClass != null) {
-            for (MethodNode methodNode : new ArrayList<MethodNode>(scriptClass.getMethods())) {
-                if (!methodNode.getName().equals("run")) {
-                    removeMethod(scriptClass, methodNode);
-                }
-            }
-        }
-
-        source.getAST().getMethods().clear();
-    }
-
-    private boolean isVisible(SourceUnit source, String className) {
-        try {
-            source.getClassLoader().loadClass(className);
-            return true;
-        } catch (ClassNotFoundException e) {
-            return false;
-        }
-    }
-
-    private void filterStatements(SourceUnit source, Spec<Statement> spec) {
-        Iterator statementIterator = source.getAST().getStatementBlock().getStatements().iterator();
-        while (statementIterator.hasNext()) {
-            Statement statement = (Statement) statementIterator.next();
-            if (!spec.isSatisfiedBy(statement)) {
-                statementIterator.remove();
-            }
-        }
-    }
-
-    public Transformer invert() {
-        return new AbstractScriptTransformer() {
-            protected int getPhase() {
-                return Phases.CANONICALIZATION;
-            }
-
-            public String getId() {
-                return "no_" + ClasspathScriptTransformer.this.getId();
-            }
-
-            @Override
-            public void call(SourceUnit source) throws CompilationFailedException {
-                Spec<Statement> spec = Specs.not(isScriptBlock());
-                filterStatements(source, spec);
-            }
-        };
-    }
-
-    public Spec<Statement> isScriptBlock() {
-        return new Spec<Statement>() {
-            public boolean isSatisfiedBy(Statement statement) {
-                if (!(statement instanceof ExpressionStatement)) {
-                    return false;
-                }
-
-                ExpressionStatement expressionStatement = (ExpressionStatement) statement;
-                if (!(expressionStatement.getExpression() instanceof MethodCallExpression)) {
-                    return false;
-                }
-
-                MethodCallExpression methodCall = (MethodCallExpression) expressionStatement.getExpression();
-                if (!isMethodOnThis(methodCall, getScriptMethodName())) {
-                    return false;
-                }
-
-                if (!(methodCall.getArguments() instanceof ArgumentListExpression)) {
-                    return false;
-                }
-
-                ArgumentListExpression args = (ArgumentListExpression) methodCall.getArguments();
-                return args.getExpressions().size() == 1 && args.getExpression(0) instanceof ClosureExpression;
-            }
-        };
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
index d481eae..16147df 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
@@ -23,7 +23,7 @@ import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainer;
 import org.gradle.api.internal.artifacts.ResolverFactory;
 import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/FixMainScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/FixMainScriptTransformer.java
deleted file mode 100755
index 806959f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/FixMainScriptTransformer.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api.internal.artifacts.dsl;
-
-import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.MethodNode;
-import org.codehaus.groovy.control.CompilationFailedException;
-import org.codehaus.groovy.control.Phases;
-import org.codehaus.groovy.control.SourceUnit;
-
-/**
- * Fixes problem where main { } inside a closure is resolved as a call to static method main(). Does this by removing
- * the static method.
- */
-public class FixMainScriptTransformer extends AbstractScriptTransformer {
-    public String getId() {
-        return "fixMain";
-    }
-
-    @Override
-    protected int getPhase() {
-        return Phases.CONVERSION;
-    }
-
-    @Override
-    public void call(SourceUnit source) throws CompilationFailedException {
-        ClassNode scriptClass = getScriptClass(source);
-        if (scriptClass == null) {
-            return;
-        }
-        for (MethodNode methodNode : scriptClass.getMethods()) {
-            if (methodNode.getName().equals("main")) {
-                removeMethod(scriptClass, methodNode);
-                break;
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/TaskDefinitionScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/TaskDefinitionScriptTransformer.java
deleted file mode 100644
index 8276ca1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/TaskDefinitionScriptTransformer.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal.artifacts.dsl;
-
-import org.codehaus.groovy.ast.CodeVisitorSupport;
-import org.codehaus.groovy.ast.DynamicVariable;
-import org.codehaus.groovy.ast.expr.*;
-import org.codehaus.groovy.control.CompilationFailedException;
-import org.codehaus.groovy.control.Phases;
-import org.codehaus.groovy.control.SourceUnit;
-
-import java.util.Collections;
-import java.util.List;
-
-public class TaskDefinitionScriptTransformer extends AbstractScriptTransformer {
-    protected int getPhase() {
-        return Phases.CANONICALIZATION;
-    }
-
-    public String getId() {
-        return "tasks";
-    }
-
-    public void call(SourceUnit source) throws CompilationFailedException {
-        visitScriptCode(source, new TaskDefinitionTransformer());
-    }
-
-    private class TaskDefinitionTransformer extends CodeVisitorSupport {
-        @Override
-        public void visitMethodCallExpression(MethodCallExpression call) {
-            doVisitMethodCallExpression(call);
-            super.visitMethodCallExpression(call);
-        }
-
-        private void doVisitMethodCallExpression(MethodCallExpression call) {
-            if (!isInstanceMethod(call, "task")) {
-                return;
-            }
-
-            ArgumentListExpression args = (ArgumentListExpression) call.getArguments();
-            if (args.getExpressions().size() == 0 || args.getExpressions().size() > 3) {
-                return;
-            }
-
-            // Matches: task <arg>{1, 3}
-
-            if (args.getExpressions().size() > 1) {
-                if (args.getExpression(0) instanceof MapExpression && args.getExpression(1) instanceof VariableExpression) {
-                    // Matches: task <name-value-pairs>, <identifier>, <arg>?
-                    // Map to: task(<name-value-pairs>, '<identifier>', <arg>?)
-                    transformVariableExpression(call, 1);
-                } else if (args.getExpression(0) instanceof VariableExpression) {
-                    // Matches: task <identifier>, <arg>?
-                    transformVariableExpression(call, 0);
-                }
-                return;
-            }
-
-            // Matches: task <arg> or task(<arg>)
-
-            Expression arg = args.getExpression(0);
-            if (arg instanceof VariableExpression) {
-                // Matches: task <identifier> or task(<identifier>)
-                transformVariableExpression(call, 0);
-            } else if (arg instanceof BinaryExpression) {
-                // Matches: task <expression> <operator> <expression>
-                transformBinaryExpression(call, (BinaryExpression) arg);
-            } else if (arg instanceof MethodCallExpression) {
-                // Matches: task <method-call>
-                maybeTransformNestedMethodCall((MethodCallExpression) arg, call);
-            }
-        }
-
-        private void transformVariableExpression(MethodCallExpression call, int index) {
-            ArgumentListExpression args = (ArgumentListExpression) call.getArguments();
-            VariableExpression arg = (VariableExpression) args.getExpression(index);
-            if (!isDynamicVar(arg)) {
-                return;
-            }
-
-            // Matches: task args?, <identifier>, args? or task(args?, <identifier>, args?)
-            // Map to: task(args?, '<identifier>', args?)
-            String taskName = arg.getText();
-            call.setMethod(new ConstantExpression("task"));
-            args.getExpressions().set(index, new ConstantExpression(taskName));
-        }
-
-        private void transformBinaryExpression(MethodCallExpression call, BinaryExpression expression) {
-
-            // Matches: task <expression> <operator> <expression>
-
-            if (expression.getLeftExpression() instanceof VariableExpression || expression.getLeftExpression() instanceof GStringExpression || expression
-                    .getLeftExpression() instanceof ConstantExpression) {
-                // Matches: task <identifier> <operator> <expression> | task <string> <operator> <expression>
-                // Map to: passThrough(task('<identifier>') <operator> <expression>) | passThrough(task(<string>) <operator> <expression>)
-                call.setMethod(new ConstantExpression("passThrough"));
-                Expression argument;
-                if (expression.getLeftExpression() instanceof VariableExpression) {
-                    argument = new ConstantExpression(expression.getLeftExpression().getText());
-                } else {
-                    argument = expression.getLeftExpression();
-                }
-                expression.setLeftExpression(new MethodCallExpression(call.getObjectExpression(), "task", argument));
-            } else if (expression.getLeftExpression() instanceof MethodCallExpression) {
-                // Matches: task <method-call> <operator> <expression>
-                MethodCallExpression transformedCall = new MethodCallExpression(call.getObjectExpression(), "task", new ArgumentListExpression());
-                boolean transformed = maybeTransformNestedMethodCall((MethodCallExpression) expression.getLeftExpression(), transformedCall);
-                if (transformed) {
-                    // Matches: task <identifier> <arg-list> <operator> <expression>
-                    // Map to: passThrough(task('<identifier>', <arg-list>) <operator> <expression>)
-                    call.setMethod(new ConstantExpression("passThrough"));
-                    expression.setLeftExpression(transformedCall);
-                }
-            }
-        }
-
-        private boolean maybeTransformNestedMethodCall(MethodCallExpression nestedMethod, MethodCallExpression target) {
-            if (!(isTaskIdentifier(nestedMethod.getMethod()) && targetIsThis(nestedMethod))) {
-                return false;
-            }
-
-            // Matches: task <identifier> <arg-list> | task <string> <arg-list>
-            // Map to: task("<identifier>", <arg-list>) | task(<string>, <arg-list>)
-
-            Expression taskName = nestedMethod.getMethod();
-            Expression mapArg = null;
-            List<Expression> extraArgs = Collections.emptyList();
-
-            if (nestedMethod.getArguments() instanceof TupleExpression) {
-                TupleExpression nestedArgs = (TupleExpression) nestedMethod.getArguments();
-                if (nestedArgs.getExpressions().size() == 2 && nestedArgs.getExpression(0) instanceof MapExpression && nestedArgs.getExpression(1) instanceof ClosureExpression) {
-                    // Matches: task <identifier>(<options-map>) <closure>
-                    mapArg = nestedArgs.getExpression(0);
-                    extraArgs = nestedArgs.getExpressions().subList(1, nestedArgs.getExpressions().size());
-                } else if (nestedArgs.getExpressions().size() == 1 && nestedArgs.getExpression(0) instanceof ClosureExpression) {
-                    // Matches: task <identifier> <closure>
-                    extraArgs = nestedArgs.getExpressions();
-                } else if (nestedArgs.getExpressions().size() == 1 && nestedArgs.getExpression(0) instanceof NamedArgumentListExpression) {
-                    // Matches: task <identifier>(<options-map>)
-                    mapArg = nestedArgs.getExpression(0);
-                } else if (nestedArgs.getExpressions().size() != 0) {
-                    return false;
-                }
-            }
-
-            target.setMethod(new ConstantExpression("task"));
-            ArgumentListExpression args = (ArgumentListExpression) target.getArguments();
-            args.getExpressions().clear();
-            if (mapArg != null) {
-                args.addExpression(mapArg);
-            }
-            args.addExpression(taskName);
-            for (Expression extraArg : extraArgs) {
-                args.addExpression(extraArg);
-            }
-            return true;
-        }
-
-        private boolean isInstanceMethod(MethodCallExpression call, String name) {
-            boolean isTaskMethod = isMethodOnThis(call, name);
-            if (!isTaskMethod) {
-                return false;
-            }
-
-            return call.getArguments() instanceof ArgumentListExpression;
-        }
-
-        private boolean isTaskIdentifier(Expression expression) {
-            return expression instanceof ConstantExpression || expression instanceof GStringExpression;
-        }
-
-        private boolean isDynamicVar(Expression expression) {
-            if (!(expression instanceof VariableExpression)) {
-                return false;
-            }
-            VariableExpression variableExpression = (VariableExpression) expression;
-            return variableExpression.getAccessedVariable() instanceof DynamicVariable;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheAccessSerializer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheAccessSerializer.java
index 0a24c09..e0a87cb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheAccessSerializer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/cache/CacheAccessSerializer.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.cache;
 
 import org.gradle.internal.Factory;
-import org.gradle.api.internal.concurrent.Synchronizer;
+import org.gradle.internal.concurrent.Synchronizer;
 
 public class CacheAccessSerializer<K, V> implements Cache<K, V> {
     
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
index e26fa2c..1b57aee 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.changedetection;
 
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.*;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java
index 8c2622c..310b159 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateCacheAccess.java
@@ -20,7 +20,7 @@ import org.gradle.api.invocation.Gradle;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentCache;
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 import org.gradle.cache.internal.FileLockManager;
 import org.gradle.listener.LazyCreationProxy;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
index 5737a5f..21dfeb3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
@@ -18,9 +18,9 @@ package org.gradle.api.internal.changedetection;
 
 import org.gradle.api.file.FileCollection;
 import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.util.ChangeListener;
 import org.gradle.util.DiffUtil;
-import org.gradle.util.IdGenerator;
 import org.gradle.util.NoOpChangeListener;
 
 import java.io.File;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java
index 14e26bb..bf1525d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateCacheAccess.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.changedetection;
 
 import org.gradle.internal.Factory;
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 
 public interface TaskArtifactStateCacheAccess {
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java
index 71f39cb..0ba1e15 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/DefaultModuleRegistry.java
@@ -17,9 +17,9 @@ package org.gradle.api.internal.classpath;
 
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.GradleDistributionLocator;
-import org.gradle.util.ClassPath;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.util.ClasspathUtil;
-import org.gradle.util.DefaultClassPath;
 import org.gradle.util.GUtil;
 
 import java.io.File;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java
index 4ec97d0..e95b9ed 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/EffectiveClassPath.java
@@ -17,8 +17,8 @@
 package org.gradle.api.internal.classpath;
 
 import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.util.ClasspathUtil;
-import org.gradle.util.DefaultClassPath;
 
 import java.io.File;
 import java.net.URI;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/Module.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/Module.java
index 7c8ff95..601a74b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/Module.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/classpath/Module.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.classpath;
 
-import org.gradle.util.ClassPath;
+import org.gradle.internal.classpath.ClassPath;
 
 import java.util.Set;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistry.java
deleted file mode 100644
index 1ee773e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistry.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal.concurrent;
-
-import org.gradle.internal.Factory;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.internal.service.UnknownServiceException;
-
-/**
- * by Szczepan Faber, created at: 11/24/11
- */
-public class SynchronizedServiceRegistry implements ServiceRegistry {
-    private final Synchronizer synchronizer = new Synchronizer();
-    private final ServiceRegistry delegate;
-
-    public SynchronizedServiceRegistry(ServiceRegistry delegate) {
-        this.delegate = delegate;
-    }
-
-    public <T> T get(final Class<T> serviceType) throws UnknownServiceException {
-        return synchronizer.synchronize(new Factory<T>() {
-            public T create() {
-                return delegate.get(serviceType);
-            }
-        });
-    }
-
-    public <T> Factory<T> getFactory(final Class<T> type) throws UnknownServiceException {
-        return synchronizer.synchronize(new Factory<Factory<T>>() {
-            public Factory<T> create() {
-                return delegate.getFactory(type);
-            }
-        });
-    }
-
-    public <T> T newInstance(final Class<T> type) throws UnknownServiceException {
-        return synchronizer.synchronize(new Factory<T>() {
-            public T create() {
-                return delegate.newInstance(type);
-            }
-        });
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/Synchronizer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/Synchronizer.java
deleted file mode 100644
index 8bc145c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/concurrent/Synchronizer.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal.concurrent;
-
-import org.gradle.api.internal.Operation;
-import org.gradle.internal.Factory;
-
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class Synchronizer {
-
-    private final Lock lock = new ReentrantLock();
-
-    public <T> T synchronize(Factory<T> factory) {
-        lock.lock();
-        try {
-            return factory.create();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    public void synchronize(Operation operation) {
-        lock.lock();
-        try {
-            operation.execute();
-        } finally {
-            lock.unlock();
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
index 69b357e..72d3c01 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
@@ -23,6 +23,7 @@ import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
 import org.gradle.api.resources.ReadableResource;
+import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 import org.gradle.internal.os.OperatingSystem;
 import org.gradle.util.GUtil;
@@ -132,9 +133,9 @@ public abstract class AbstractFileResolver implements FileResolver {
         return new File(current, segment);
     }
 
-    public FileSource resolveLater(final Object path) {
-        return new FileSource() {
-            public File get() {
+    public Factory<File> resolveLater(final Object path) {
+        return new Factory<File>() {
+            public File create() {
                 return resolve(path);
             }
         };
@@ -179,8 +180,8 @@ public abstract class AbstractFileResolver implements FileResolver {
                 } catch (Exception e) {
                     throw new RuntimeException(e);
                 }
-            } else if (current instanceof FileSource) {
-                return ((FileSource) current).get();
+            } else if (current instanceof Factory) {
+                return ((Factory) current).create();
             } else {
                 return current;
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
index a2a0945..1872cea 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.file;
 
 import org.gradle.api.Nullable;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.Factory;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.GUtil;
 
@@ -25,18 +26,18 @@ import java.io.File;
 import java.io.IOException;
 
 public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
-    private final FileSource baseDir;
+    private final Factory<File> baseDirFactory;
 
-    public DefaultTemporaryFileProvider(FileSource baseDir) {
-        this.baseDir = baseDir;
+    public DefaultTemporaryFileProvider(final Factory<File> fileFactory) {
+        this.baseDirFactory = fileFactory;
     }
 
     public File newTemporaryFile(String... path) {
-        return GFileUtils.canonicalise(new File(baseDir.get(), GUtil.join(path, "/")));
+        return GFileUtils.canonicalise(new File(baseDirFactory.create(), GUtil.join(path, "/")));
     }
 
     public File createTemporaryFile(String prefix, @Nullable String suffix, String... path) {
-        File dir = new File(baseDir.get(), GUtil.join(path, "/"));
+        File dir = new File(baseDirFactory.create(), GUtil.join(path, "/"));
         GFileUtils.createDirectory(dir);
         try {
             return File.createTempFile(prefix, suffix, dir);
@@ -46,7 +47,7 @@ public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
     }
 
     public File createTemporaryDirectory(@Nullable String prefix, @Nullable String suffix, @Nullable String... path) {
-        File dir = new File(baseDir.get(), GUtil.join(path, "/"));
+        File dir = new File(baseDirFactory.create(), GUtil.join(path, "/"));
         GFileUtils.createDirectory(dir);
         try {
             // TODO: This is not a great paradigm for creating a temporary directory.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
index 3c6168c..8e05cae 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
@@ -19,6 +19,7 @@ import org.gradle.api.PathValidation;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.resources.ReadableResource;
+import org.gradle.internal.Factory;
 
 import java.io.File;
 import java.net.URI;
@@ -30,7 +31,7 @@ public interface FileResolver {
 
     File resolve(Object path, PathValidation validation);
 
-    FileSource resolveLater(Object path);
+    Factory<File> resolveLater(Object path);
     
     FileCollection resolveFiles(Object... paths);
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileSource.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileSource.java
deleted file mode 100644
index 07ef62d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileSource.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal.file;
-
-import java.io.File;
-
-public interface FileSource {
-    File get();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/RelativeFile.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/RelativeFile.java
new file mode 100644
index 0000000..bcc61f8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/RelativeFile.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.file;
+
+import org.gradle.api.file.RelativePath;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class RelativeFile implements Serializable {
+
+    private final File file;
+    private final RelativePath relativePath;
+
+    public RelativeFile(File file, RelativePath relativePath) {
+        this.file = file;
+        this.relativePath = relativePath;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public RelativePath getRelativePath() {
+        return relativePath;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TmpDirTemporaryFileProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TmpDirTemporaryFileProvider.java
index 53704f0..5d3bd68 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TmpDirTemporaryFileProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/TmpDirTemporaryFileProvider.java
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.file;
 
 import org.gradle.api.Nullable;
+import org.gradle.internal.Factory;
 import org.gradle.internal.SystemProperties;
 import org.gradle.util.GFileUtils;
 
@@ -28,8 +29,8 @@ public class TmpDirTemporaryFileProvider extends DefaultTemporaryFileProvider {
     private final List<File> createdFiles = new ArrayList<File>();
 
     public TmpDirTemporaryFileProvider() {
-        super(new FileSource() {
-            public File get() {
+        super(new Factory<File>() {
+            public File create() {
                 return GFileUtils.canonicalise(new File(SystemProperties.getJavaIoTmpDir()));
             }
         });
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
index f18670a..36bf5ce 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
@@ -20,6 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.api.file.*;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.specs.Spec;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 
 import java.io.FilterReader;
 import java.util.ArrayList;
@@ -41,7 +42,7 @@ public class CopyActionImpl implements CopyAction, CopySpecSource {
         this.resolver = resolver;
         root = new CopySpecImpl(resolver);
         mainContent = root.addChild();
-        this.visitor = new MappingCopySpecVisitor(new NormalizingCopySpecVisitor(visitor));
+        this.visitor = new MappingCopySpecVisitor(new NormalizingCopySpecVisitor(visitor), FileSystems.getDefault());
     }
 
     public FileResolver getResolver() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java
index e927524..923fc72 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java
@@ -16,8 +16,9 @@
 package org.gradle.api.internal.file.copy;
 
 import org.gradle.api.file.DeleteAction;
+import org.gradle.api.file.UnableToDeleteFileException;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.util.GFileUtils;
+import org.gradle.internal.os.OperatingSystem;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -31,25 +32,66 @@ public class DeleteActionImpl implements DeleteAction {
     
     private FileResolver fileResolver;
 
+    private static final int DELETE_RETRY_SLEEP_MILLIS = 10;
+
     public DeleteActionImpl(FileResolver fileResolver) {
         this.fileResolver = fileResolver;
     }
 
     public boolean delete(Object... deletes) {
         boolean didWork = false;
-
         for (File file : fileResolver.resolveFiles(deletes)) {
             if (!file.exists()) {
                 continue;
             }
             logger.debug("Deleting {}", file);
             didWork = true;
-            if (file.isFile()) {
-                GFileUtils.deleteQuietly(file);
-            } else {
-                GFileUtils.deleteDirectory(file);
-            }
+            doDelete(file);
         }
         return didWork;
     }
+
+    private void doDelete(File file) {
+        if (file.isDirectory()) {
+            File[] contents = file.listFiles();
+
+            // Something else may have removed it
+            if (contents == null) {
+                return;
+            }
+
+            for (File item : contents) {
+                doDelete(item);
+            }
+        }
+
+        if (!file.delete() && file.exists()) {
+            handleFailedDelete(file);
+
+        }
+    }
+
+    private boolean isRunGcOnFailedDelete() {
+        return OperatingSystem.current().isWindows();
+    }
+
+    private void handleFailedDelete(File file) {
+        // This is copied from Ant (see org.apache.tools.ant.util.FileUtils.tryHardToDelete).
+        // It mentions that there is a bug in the Windows JDK impls that this is a valid
+        // workaround for. I've been unable to find a definitive reference to this bug.
+        // The thinking is that if this is good enough for Ant, it's good enough for us.
+        if (isRunGcOnFailedDelete()) {
+            System.gc();
+        }
+        try {
+            Thread.sleep(DELETE_RETRY_SLEEP_MILLIS);
+        } catch (InterruptedException ex) {
+            // Ignore Exception
+        }
+
+        if (!file.delete() && file.exists()) {
+            throw new UnableToDeleteFileException(file);
+        }
+    }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
index 5efdd32..a662c9d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
@@ -23,16 +23,18 @@ import org.gradle.api.file.FileCopyDetails;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.AbstractFileTreeElement;
-import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 
 import java.io.*;
 import java.util.Map;
 
 public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
     private ReadableCopySpec spec;
+    private FileSystem fileSystem;
 
-    public MappingCopySpecVisitor(CopySpecVisitor visitor) {
+    public MappingCopySpecVisitor(CopySpecVisitor visitor, FileSystem fileSystem) {
         super(visitor);
+        this.fileSystem = fileSystem;
     }
 
     public void visitSpec(ReadableCopySpec spec) {
@@ -41,11 +43,11 @@ public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
     }
 
     public void visitDir(FileVisitDetails dirDetails) {
-        getVisitor().visitDir(new FileVisitDetailsImpl(dirDetails, spec));
+        getVisitor().visitDir(new FileVisitDetailsImpl(dirDetails, spec, fileSystem));
     }
 
     public void visitFile(final FileVisitDetails fileDetails) {
-        FileVisitDetailsImpl details = new FileVisitDetailsImpl(fileDetails, spec);
+        FileVisitDetailsImpl details = new FileVisitDetailsImpl(fileDetails, spec, fileSystem);
         for (Action<? super FileCopyDetails> action : spec.getAllCopyActions()) {
             action.execute(details);
             if (details.excluded) {
@@ -58,14 +60,16 @@ public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
     private static class FileVisitDetailsImpl extends AbstractFileTreeElement implements FileVisitDetails, FileCopyDetails {
         private final FileVisitDetails fileDetails;
         private final ReadableCopySpec spec;
+        private FileSystem fileSystem;
         private final FilterChain filterChain = new FilterChain();
         private RelativePath relativePath;
         private boolean excluded;
         private Integer mode;
 
-        public FileVisitDetailsImpl(FileVisitDetails fileDetails, ReadableCopySpec spec) {
+        public FileVisitDetailsImpl(FileVisitDetails fileDetails, ReadableCopySpec spec, FileSystem fileSystem) {
             this.fileDetails = fileDetails;
             this.spec = spec;
+            this.fileSystem = fileSystem;
         }
 
         public String getDisplayName() {
@@ -123,16 +127,16 @@ public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
                 return super.copyTo(target);
             } else {
                 final boolean copied = fileDetails.copyTo(target);
-                adaptPermissionSpecs(target);
+                adaptPermissions(target);
                 return copied;
             }
         }
 
-        private void adaptPermissionSpecs(File target) {
-            final Integer specMode = getSpecMode();
+        private void adaptPermissions(File target) {
+            final Integer specMode = getMode();
             if(specMode !=null){
                 try {
-                    FileSystems.getDefault().chmod(target, specMode);
+                    fileSystem.chmod(target, specMode);
                 } catch (IOException e) {
                     throw new GradleException(String.format("Could not set permission %s on '%s'.", specMode, target), e);
                 }
@@ -223,4 +227,4 @@ public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
             size += len;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java
index faf8711..edeeea9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java
@@ -21,7 +21,7 @@ import groovy.lang.MissingPropertyException;
 import org.gradle.api.GradleException;
 import org.gradle.api.internal.BeanDynamicObject;
 import org.gradle.api.internal.DynamicObject;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.plugins.Convention;
 import org.gradle.api.plugins.ExtraPropertiesExtension;
 import org.gradle.util.DeprecationLogger;
@@ -45,7 +45,7 @@ public class DefaultConvention implements Convention {
      *
      * It's here for backwards compatibility with our tests and for convenience.
      *
-     * @see #DefaultConvention(org.gradle.api.internal.Instantiator)
+     * @see #DefaultConvention(org.gradle.internal.reflect.Instantiator)
      */
     public DefaultConvention() {
         this(null);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
index 21239d0..e4d5b45 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
@@ -51,6 +51,7 @@ import org.gradle.configuration.ScriptPlugin;
 import org.gradle.configuration.ScriptPluginFactory;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.StandardOutputCapture;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
index 4ce083e..9782958 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
@@ -20,6 +20,7 @@ import org.gradle.api.internal.project.ant.AntLoggingAdapter
 import org.gradle.api.internal.project.ant.BasicAntBuilder
 import org.gradle.util.*
 import org.gradle.internal.jvm.Jvm
+import org.gradle.internal.classpath.DefaultClassPath
 
 class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {
     private final Map<List<File>, ClassLoader> baseClassloaders = [:]
@@ -65,8 +66,7 @@ class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {
             if (toolsJar) {
                 fullClasspath += toolsJar
             }
-            List<URI> classpathUrls = fullClasspath.collect { it.toURI() }
-            baseLoader = classLoaderFactory.createIsolatedClassLoader(classpathUrls)
+            baseLoader = classLoaderFactory.createIsolatedClassLoader(new DefaultClassPath(fullClasspath))
             baseClassloaders[baseClasspath] = baseLoader
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
index 6a8055c..e247fbb 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
@@ -30,6 +30,8 @@ import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.DefaultListenerManager;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
index 601598f..3fe76da 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal.project;
 
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.groovy.scripts.ScriptSource;
 import org.gradle.groovy.scripts.StringScriptSource;
 import org.gradle.groovy.scripts.UriScriptSource;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
index 6db671a..01a5ba6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
@@ -21,7 +21,7 @@ import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
 import org.gradle.api.internal.artifacts.DefaultModule;
@@ -78,8 +78,8 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
     }
 
     protected TemporaryFileProvider createTemporaryFileProvider() {
-        return new DefaultTemporaryFileProvider(new FileSource() {
-            public File get() {
+        return new DefaultTemporaryFileProvider(new Factory<File>() {
+            public File create() {
                 return new File(project.getBuildDir(), "tmp");
             }
         });
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
index ab1203b..3b02320 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.project;
 import org.gradle.StartParameter;
 import org.gradle.api.execution.TaskActionListener;
 import org.gradle.api.internal.changedetection.*;
+import org.gradle.internal.id.RandomLongIdGenerator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.api.internal.tasks.TaskExecuter;
 import org.gradle.api.internal.tasks.execution.*;
@@ -25,7 +26,6 @@ import org.gradle.api.invocation.Gradle;
 import org.gradle.cache.CacheRepository;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerManager;
-import org.gradle.util.RandomLongIdGenerator;
 
 public class TaskExecutionServices extends DefaultServiceRegistry {
     private final Gradle gradle;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
index c225988..7d223ce 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
@@ -44,14 +44,19 @@ import org.gradle.groovy.scripts.ScriptExecutionListener;
 import org.gradle.groovy.scripts.internal.*;
 import org.gradle.initialization.*;
 import org.gradle.internal.Factory;
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.TrueTimeProvider;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.LongIdGenerator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceLocator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.messaging.actor.internal.DefaultActorFactory;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
 import org.gradle.messaging.remote.MessagingServer;
 import org.gradle.process.internal.DefaultWorkerProcessFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpec.java
new file mode 100644
index 0000000..8402af4
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpec.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.specs;
+
+import org.gradle.api.specs.Spec;
+
+/**
+ * A predicate against objects of type T that can explain the unsatisfied reason.
+ *
+ * @param <T> The target type for this Spec
+ */
+public interface ExplainingSpec<T> extends Spec<T> {
+
+    /**
+     * Explains why the spec is not satisfied.
+     *
+     * @param element candidate
+     * @return the description. Must not be null if the spec is not satisfied. Is null if spec is satisfied.
+     */
+    String whyUnsatisfied(T element);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpecs.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpecs.java
new file mode 100644
index 0000000..8747618
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/specs/ExplainingSpecs.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.specs;
+
+/**
+ * by Szczepan Faber, created at: 5/14/12
+ */
+public class ExplainingSpecs {
+
+    private static final ExplainingSpec<Object> SATISFIES_ALL = new ExplainingSpec<Object>() {
+        public boolean isSatisfiedBy(Object element) {
+            return true;
+        }
+        public String whyUnsatisfied(Object element) {
+            return null;
+        }
+    };
+
+    public static <T> ExplainingSpec<T> satisfyAll() {
+        return (ExplainingSpec<T>)SATISFIES_ALL;
+    }
+
+    private static final ExplainingSpec<Object> SATISFIES_NONE = new ExplainingSpec<Object>() {
+        public boolean isSatisfiedBy(Object element) {
+            return false;
+        }
+        public String whyUnsatisfied(Object element) {
+            return "Never satisfies any.";
+        }
+    };
+
+    public static <T> ExplainingSpec<T> satisfyNone() {
+        return (ExplainingSpec<T>)SATISFIES_NONE;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java
index 14cb7a0..54bbc86 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java
@@ -21,7 +21,7 @@ import org.gradle.api.Task;
 import org.gradle.api.UnknownDomainObjectException;
 import org.gradle.api.UnknownTaskException;
 import org.gradle.api.internal.DefaultNamedDomainObjectSet;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.collections.CollectionEventRegister;
 import org.gradle.api.internal.collections.CollectionFilter;
 import org.gradle.api.internal.project.ProjectInternal;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
index e4e5af9..ca19b47 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
@@ -22,7 +22,7 @@ import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.UnknownTaskException;
 import org.gradle.api.internal.DynamicObject;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.NamedDomainObjectContainerConfigureDelegate;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
index 49663bc..edd84e0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal.tasks;
 
 import org.gradle.api.Project;
 import org.gradle.internal.Factory;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 
 public class DefaultTaskContainerFactory implements Factory<TaskContainerInternal> {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java b/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
index 6c059d9..4107c32 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/logging/LogLevel.java
@@ -15,14 +15,12 @@
  */
 package org.gradle.api.logging;
 
-import java.io.Serializable;
-
 /**
  * The log levels supported by Gradle.
  *
  * @author Hans Dockter
  */
-public enum LogLevel implements Serializable {
+public enum LogLevel {
     DEBUG {
         boolean isEnabled(Logger logger) {
             return logger.isDebugEnabled();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/Specs.java b/subprojects/core/src/main/groovy/org/gradle/api/specs/Specs.java
index b7a2334..e480735 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/specs/Specs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/specs/Specs.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.specs;
 
+import org.gradle.api.specs.internal.ClosureSpec;
 import org.gradle.util.DeprecationLogger;
 
 import groovy.lang.Closure;
@@ -49,13 +50,8 @@ public class Specs {
         return (Spec<T>)SATISFIES_NONE;
     }
 
-    public static <T> Spec<T> convertClosureToSpec(final Closure cl) {
-        return new Spec<T>() {
-            public boolean isSatisfiedBy(T element) {
-                Object value = cl.call(element);
-                return value == null ? false : ((Boolean) value).booleanValue();
-            }
-        };
+    public static <T> Spec<T> convertClosureToSpec(final Closure closure) {
+        return new ClosureSpec<T>(closure);
     }
 
     public static <T> Set<T> filterIterable(Iterable<? extends T> iterable, Spec<? super T> spec) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/specs/internal/ClosureSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/specs/internal/ClosureSpec.java
new file mode 100644
index 0000000..2610110
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/specs/internal/ClosureSpec.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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 org.gradle.api.specs.internal;
+
+import groovy.lang.Closure;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.gradle.api.specs.Spec;
+
+public class ClosureSpec<T> implements Spec<T> {
+
+    private final Closure<?> closure;
+
+    public ClosureSpec(Closure<?> closure) {
+        this.closure = closure;
+    }
+
+    public boolean isSatisfiedBy(T element) {
+        Object value = closure.call(element);
+        return (Boolean)InvokerHelper.invokeMethod(value, "asBoolean", null);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
index 9692a69..6b610b8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
@@ -73,7 +73,7 @@ public abstract class AbstractCopyTask extends ConventionTask implements CopySpe
         }else{
             return DeprecationLogger.whileDisabled(new Factory<FileCollection>() {
                 public FileCollection create() {
-                    return getDefaultSource(); //To change body of implemented methods use File | Settings | File Templates.
+                    return getDefaultSource();
                 }
             });
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Input.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Input.java
index bedccef..ae48955 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Input.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Input.java
@@ -1,31 +1,29 @@
-/*
- * Copyright 2010 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 org.gradle.api.tasks;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * <p>Attached to a task property to indicate that the property specifies some input value for the task.</p>
- *
- * <p>This annotation should be attached to the getter method or the field for the property.</p>
- */
- at Retention(RetentionPolicy.RUNTIME)
- at Target({ElementType.METHOD, ElementType.FIELD})
-public @interface Input {
-}
+/*
+ * Copyright 2010 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 org.gradle.api.tasks;
+
+import java.lang.annotation.*;
+
+/**
+ * <p>Attached to a task property to indicate that the property specifies some input value for the task.</p>
+ *
+ * <p>This annotation should be attached to the getter method or the field for the property.</p>
+ */
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface Input {
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputDirectory.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputDirectory.java
index 8ba1941..3130c73 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputDirectory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputDirectory.java
@@ -15,10 +15,7 @@
  */
 package org.gradle.api.tasks;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
 /**
  * <p>Marks a property as specifying an input directory for a task.</p> <p>This annotation should be attached to the
@@ -26,6 +23,7 @@ import java.lang.annotation.Target;
  *
  * <p>This annotation should be attached to the getter method or the field for the property.</p>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface InputDirectory {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputFile.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputFile.java
index 9ba2862..3a6c1e3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputFile.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputFile.java
@@ -15,16 +15,14 @@
  */
 package org.gradle.api.tasks;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
 /**
  * <p>Marks a property as specifying an input file for a task.</p>
  *
  * <p>This annotation should be attached to the getter method or the field for the property.</p>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface InputFile {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputFiles.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputFiles.java
index 301cd7a..3813185 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputFiles.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/InputFiles.java
@@ -15,16 +15,14 @@
  */
 package org.gradle.api.tasks;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
 /**
  * <p>Marks a property as specifying the input files for a task.</p>
  *
  * <p>This annotation should be attached to the getter method or the field for the property.</p>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface InputFiles {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
index 5552289..541830d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
@@ -45,7 +45,7 @@ public class JavaExec extends ConventionTask implements JavaExecSpec {
     }
 
     @TaskAction
-    void exec() {
+    public void exec() {
         setMain(getMain()); // make convention mapping work (at least for 'main')
         javaExecHandleBuilder.execute();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Optional.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Optional.java
index b670e36..2da3033 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Optional.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Optional.java
@@ -15,10 +15,7 @@
  */
 package org.gradle.api.tasks;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
 /**
  * <p>Marks a task property as optional. This means that a value does not have to be specified for the property, but any
@@ -38,6 +35,7 @@ import java.lang.annotation.Target;
  *
  * <li>{@link org.gradle.api.tasks.OutputDirectory}</li> </ul>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
 public @interface Optional {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectories.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectories.java
index 96ac77a..37be99f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectories.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectories.java
@@ -16,16 +16,14 @@
 
 package org.gradle.api.tasks;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
 /**
  * <p>Marks a property as specifying one or more output directories for a task.</p>
  *
  * <p>This annotation should be attached to the getter method or the field for the property.</p>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface OutputDirectories {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectory.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectory.java
index 102b9cb..f0c98a0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputDirectory.java
@@ -15,16 +15,14 @@
  */
 package org.gradle.api.tasks;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.annotation.ElementType;
+import java.lang.annotation.*;
 
 /**
  * <p>Marks a property as specifying an output directory for a task.</p>
  *
  * <p>This annotation should be attached to the getter method or the field for the property.</p>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface OutputDirectory {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFile.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFile.java
index 3d4c621..b802536 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFile.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFile.java
@@ -15,16 +15,14 @@
  */
 package org.gradle.api.tasks;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.annotation.ElementType;
+import java.lang.annotation.*;
 
 /**
  * <p>Marks a property as specifying an output file for a task.</p>
  *
  * <p>This annotation should be attached to the getter method or the field for the property.</p>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface OutputFile {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFiles.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFiles.java
index 3a3fcb9..103ca98 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFiles.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/OutputFiles.java
@@ -16,16 +16,14 @@
 
 package org.gradle.api.tasks;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
 /**
  * <p>Marks a property as specifying one or more output files for a task.</p>
  *
  * <p>This annotation should be attached to the getter method or the field for the property.</p>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface OutputFiles {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/SkipWhenEmpty.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/SkipWhenEmpty.java
index ebd21fd..c691855 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/SkipWhenEmpty.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/SkipWhenEmpty.java
@@ -15,10 +15,7 @@
  */
 package org.gradle.api.tasks;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.annotation.ElementType;
+import java.lang.annotation.*;
 
 /**
  * <p>Attached to a task property to indicate that the task should be skipped when the value of the property is an empty
@@ -30,6 +27,7 @@ import java.lang.annotation.ElementType;
  *
  * <li>{@link org.gradle.api.tasks.InputDirectory}</li> </ul>
  */
+ at Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD, ElementType.FIELD})
 public @interface SkipWhenEmpty {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternFilterable.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternFilterable.java
index 736f30c..14434c9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternFilterable.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/util/PatternFilterable.java
@@ -15,13 +15,12 @@
  */
 package org.gradle.api.tasks.util;
 
-import org.gradle.api.specs.Spec;
+import groovy.lang.Closure;
 import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.specs.Spec;
 
 import java.util.Set;
 
-import groovy.lang.Closure;
-
 /**
  * <p>A {@code PatternFilterable} represents some file container which Ant-style include and exclude patterns or specs
  * can be applied to.</p>
@@ -192,14 +191,23 @@ public interface PatternFilterable {
 
     /**
      * Adds an exclude spec. This method may be called multiple times to append new specs.The given closure is passed a
-     * {@link org.gradle.api.file.FileTreeElement} as its parameter.
+     * {@link org.gradle.api.file.FileTreeElement} as its parameter. The closure should return true or false. Example:
+     *
+     * <pre autoTested='true'>
+     * copySpec {
+     *   from 'source'
+     *   into 'destination'
+     *   //an example of excluding files from certain configuration:
+     *   exclude { it.file in configurations.someConf.files }
+     * }
+     * </pre>
      *
      * If excludes are not provided, then no files will be excluded. If excludes are provided, then files must not match
      * any exclude pattern to be processed.
      *
      * @param excludeSpec the spec to add
      * @return this
-     * @see PatternFilterable Pattern Format
+     * @see FileTreeElement
      */
     PatternFilterable exclude(Closure excludeSpec);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java b/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
index 7825645..1b71ee5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
@@ -15,7 +15,8 @@
  */
 package org.gradle.cache;
 
-import org.gradle.util.ClassLoaderObjectInputStream;
+import org.gradle.internal.io.ClassLoaderObjectInputStream;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.*;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java b/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java
index cca3769..031096f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/ObjectCacheBuilder.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.cache;
 
+import org.gradle.messaging.serialize.Serializer;
+
 import java.util.Map;
 
 public interface ObjectCacheBuilder<E, T> extends CacheBuilder<T> {
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
index a203490..8f81c58 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/PersistentCache.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.cache;
 
+import org.gradle.messaging.serialize.Serializer;
+
 import java.io.File;
 
 /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/Serializer.java b/subprojects/core/src/main/groovy/org/gradle/cache/Serializer.java
deleted file mode 100644
index 22ffd83..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/cache/Serializer.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.cache;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public interface Serializer<T> {
-    T read(InputStream instr) throws Exception;
-
-    void write(OutputStream outstr, T value) throws Exception;
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/AbstractFileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/AbstractFileAccess.java
index 75202ba..1ed696d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/AbstractFileAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/AbstractFileAccess.java
@@ -21,8 +21,8 @@ import org.gradle.internal.UncheckedException;
 import java.util.concurrent.Callable;
 
 public abstract class AbstractFileAccess implements FileAccess {
-    public <T> T readFromFile(final Callable<? extends T> action) throws LockTimeoutException {
-        return readFromFile(new Factory<T>() {
+    public <T> T readFile(final Callable<? extends T> action) throws LockTimeoutException, FileIntegrityViolationException {
+        return readFile(new Factory<T>() {
             public T create() {
                 try {
                     return action.call();
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java
index 1b8d443..2290aac 100755
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/CacheFactory.java
@@ -19,6 +19,7 @@ import org.gradle.CacheUsage;
 import org.gradle.api.Action;
 import org.gradle.cache.*;
 import org.gradle.cache.internal.FileLockManager.LockMode;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.File;
 import java.util.Map;
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
index d8d3438..f57a7f6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheAccess.java
@@ -20,7 +20,7 @@ import org.gradle.internal.Factory;
 import org.gradle.cache.CacheAccess;
 import org.gradle.cache.DefaultSerializer;
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
 import org.gradle.internal.UncheckedException;
 
@@ -277,12 +277,16 @@ public class DefaultCacheAccess implements CacheAccess {
     }
 
     private class UnitOfWorkFileAccess extends AbstractFileAccess {
-        public <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException {
-            return getLock().readFromFile(action);
+        public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException {
+            return getLock().readFile(action);
         }
 
-        public void writeToFile(Runnable action) throws LockTimeoutException {
-            getLock().writeToFile(action);
+        public void updateFile(Runnable action) throws LockTimeoutException {
+            getLock().updateFile(action);
+        }
+
+        public void writeFile(Runnable action) throws LockTimeoutException {
+            getLock().writeFile(action);
         }
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
index 50c4131..bb4d303 100755
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheFactory.java
@@ -20,6 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.internal.Factory;
 import org.gradle.cache.*;
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache;
+import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
@@ -58,10 +59,19 @@ public class DefaultCacheFactory implements Factory<CacheFactory> {
             File canonicalDir = GFileUtils.canonicalise(cacheDir);
             DirCacheReference dirCacheReference = dirCaches.get(canonicalDir);
             if (dirCacheReference == null) {
-                DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(canonicalDir, displayName, usage, validator, properties, lockMode, action, lockManager);
-                cache.open();
-                dirCacheReference = new DirCacheReference(cache, properties, lockMode);
-                dirCaches.put(canonicalDir, dirCacheReference);
+                if (lockMode.equals(LockMode.None)) {
+                    // Create nested cache with LockMode#Exclusive (tb discussed) that is opened and closed on Demand in the DelegateOnDemandPersistentDirectoryCache.
+                    DefaultPersistentDirectoryCache nestedCache = new DefaultPersistentDirectoryCache(canonicalDir, displayName, usage, validator, properties, LockMode.Exclusive, action, lockManager);
+                    DelegateOnDemandPersistentDirectoryCache onDemandDache = new DelegateOnDemandPersistentDirectoryCache(nestedCache);
+                    onDemandDache.open();
+                    dirCacheReference = new DirCacheReference(onDemandDache, properties, lockMode);
+                    dirCaches.put(canonicalDir, dirCacheReference);
+                } else {
+                    ReferencablePersistentCache cache = new DefaultPersistentDirectoryCache(canonicalDir, displayName, usage, validator, properties, lockMode, action, lockManager);
+                    cache.open();
+                    dirCacheReference = new DirCacheReference(cache, properties, lockMode);
+                    dirCaches.put(canonicalDir, dirCacheReference);
+                }
             } else {
                 if (usage == CacheUsage.REBUILD && dirCacheReference.rebuiltBy != this) {
                     throw new IllegalStateException(String.format("Cannot rebuild cache '%s' as it is already open.", cacheDir));
@@ -87,7 +97,7 @@ public class DefaultCacheFactory implements Factory<CacheFactory> {
             File canonicalDir = GFileUtils.canonicalise(storeDir);
             DirCacheReference dirCacheReference = dirCaches.get(canonicalDir);
             if (dirCacheReference == null) {
-                DefaultPersistentDirectoryStore cache = new DefaultPersistentDirectoryStore(canonicalDir, displayName, lockMode, lockManager);
+                ReferencablePersistentCache cache = new DefaultPersistentDirectoryStore(canonicalDir, displayName, lockMode, lockManager);
                 cache.open();
                 dirCacheReference = new DirCacheReference(cache, Collections.<String, Object>emptyMap(), lockMode);
                 dirCaches.put(canonicalDir, dirCacheReference);
@@ -160,14 +170,14 @@ public class DefaultCacheFactory implements Factory<CacheFactory> {
         }
     }
 
-    private class DirCacheReference extends BasicCacheReference<DefaultPersistentDirectoryStore> {
+    private class DirCacheReference extends BasicCacheReference<ReferencablePersistentCache> {
         private final Map<String, ?> properties;
         private final FileLockManager.LockMode lockMode;
         IndexedCacheReference indexedCache;
         StateCacheReference stateCache;
         CacheFactoryImpl rebuiltBy;
 
-        public DirCacheReference(DefaultPersistentDirectoryStore cache, Map<String, ?> properties, FileLockManager.LockMode lockMode) {
+        public DirCacheReference(ReferencablePersistentCache cache, Map<String, ?> properties, FileLockManager.LockMode lockMode) {
             super(cache);
             this.properties = properties;
             this.lockMode = lockMode;
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
index 50dae8f..9790352 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultCacheRepository.java
@@ -19,6 +19,7 @@ import org.gradle.CacheUsage;
 import org.gradle.api.Action;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.cache.*;
+import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.GradleVersion;
 
 import java.io.File;
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
index 03520b3..0a0f182 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultFileLockManager.java
@@ -21,21 +21,15 @@ import org.gradle.util.GFileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.EOFException;
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
+import java.io.*;
 import java.util.Set;
-import java.util.concurrent.Callable;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
  * Uses file system locks on a lock file per target file. Each lock file is made up of 2 regions:
  *
- * <ul>
- *     <li>State region: 1 byte version field, 1 byte clean flag.</li>
- *     <li>Owner information region: 1 byte version field, utf-8 encoded owner process id, utf-8 encoded owner operation display name.</li>
- * </ul>
+ * <ul> <li>State region: 1 byte version field, 1 byte clean flag.</li> <li>Owner information region: 1 byte version field, utf-8 encoded owner process id, utf-8 encoded owner operation display
+ * name.</li> </ul>
  */
 public class DefaultFileLockManager implements FileLockManager {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFileLockManager.class);
@@ -45,6 +39,8 @@ public class DefaultFileLockManager implements FileLockManager {
     private static final int STATE_REGION_POS = 0;
     private static final byte INFORMATION_REGION_PROTOCOL = 2;
     private static final int INFORMATION_REGION_POS = STATE_REGION_POS + STATE_REGION_SIZE;
+    public static final int INFORMATION_REGION_SIZE = 2048;
+    public static final int INFORMATION_REGION_DESCR_CHUNK_LIMIT = 340;
     private final Set<File> lockedFiles = new CopyOnWriteArraySet<File>();
     private final ProcessMetaDataProvider metaDataProvider;
 
@@ -80,10 +76,15 @@ public class DefaultFileLockManager implements FileLockManager {
         private final String operationDisplayName;
         private java.nio.channels.FileLock lock;
         private RandomAccessFile lockFileAccess;
+        private boolean integrityViolated;
 
         public DefaultFileLock(File target, LockMode mode, String displayName, String operationDisplayName) throws Throwable {
+            if (mode == LockMode.None) {
+                throw new UnsupportedOperationException("Locking mode None is not supported.");
+            }
+
             this.target = target;
-            this.mode = mode;
+
             this.displayName = displayName;
             this.operationDisplayName = operationDisplayName;
             if (target.isDirectory()) {
@@ -91,16 +92,20 @@ public class DefaultFileLockManager implements FileLockManager {
             } else {
                 lockFile = new File(target.getParentFile(), target.getName() + ".lock");
             }
+
             lockFile.getParentFile().mkdirs();
             lockFile.createNewFile();
             lockFileAccess = new RandomAccessFile(lockFile, "rw");
             try {
                 lock = lock(mode);
+                integrityViolated = !getUnlockedCleanly();
             } catch (Throwable t) {
                 // Also releases any locks
                 lockFileAccess.close();
                 throw t;
             }
+
+            this.mode = lock.isShared() ? LockMode.Shared : LockMode.Exclusive;
         }
 
         public boolean isLockFile(File file) {
@@ -108,48 +113,49 @@ public class DefaultFileLockManager implements FileLockManager {
         }
 
         public boolean getUnlockedCleanly() {
-            return readFromFile(new Callable<Boolean>() {
-                public Boolean call() throws Exception {
-                    try {
-                        lockFileAccess.seek(STATE_REGION_POS + 1);
-                        if (!lockFileAccess.readBoolean()) {
-                            // Process has crashed while updating target file
-                            return false;
-                        }
-                    } catch (EOFException e) {
-                        // Process has crashed writing to lock file
-                        return false;
-                    }
-                    return true;
+            assertOpen();
+            try {
+                lockFileAccess.seek(STATE_REGION_POS + 1);
+                if (!lockFileAccess.readBoolean()) {
+                    // Process has crashed while updating target file
+                    return false;
                 }
-            });
+            } catch (EOFException e) {
+                // Process has crashed writing to lock file
+                return false;
+            } catch (Exception e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+
+            return true;
         }
 
-        public <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException {
-            assertOpen();
+        public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException, FileIntegrityViolationException {
+            assertOpenAndIntegral();
             return action.create();
         }
 
-        public void writeToFile(Runnable action) {
+        public void updateFile(Runnable action) throws LockTimeoutException, FileIntegrityViolationException {
+            assertOpenAndIntegral();
+            doWriteAction(action);
+        }
+
+        public void writeFile(Runnable action) throws LockTimeoutException {
             assertOpen();
+            doWriteAction(action);
+        }
+
+        private void doWriteAction(Runnable action) {
+            if (mode != LockMode.Exclusive) {
+                throw new InsufficientLockModeException("An exclusive lock is required for this operation");
+            }
+
             try {
-                // TODO - need to escalate without releasing lock
-                java.nio.channels.FileLock updateLock = null;
-                if (mode != LockMode.Exclusive) {
-                    lock.release();
-                    lock = null;
-                    updateLock = lock(LockMode.Exclusive);
-                }
-                try {
-                    markDirty();
-                    action.run();
-                    markClean();
-                } finally {
-                    if (mode != LockMode.Exclusive) {
-                        updateLock.release();
-                        lock = lock(mode);
-                    }
-                }
+                integrityViolated = true;
+                markDirty();
+                action.run();
+                markClean();
+                integrityViolated = false;
             } catch (Throwable t) {
                 throw UncheckedException.throwAsUncheckedException(t);
             }
@@ -161,6 +167,13 @@ public class DefaultFileLockManager implements FileLockManager {
             }
         }
 
+        private void assertOpenAndIntegral() {
+            assertOpen();
+            if (integrityViolated) {
+                throw new FileIntegrityViolationException(String.format("The file '%s' was not unlocked cleanly", target));
+            }
+        }
+
         private void markClean() throws IOException {
             lockFileAccess.seek(STATE_REGION_POS);
             lockFileAccess.writeByte(STATE_REGION_PROTOCOL);
@@ -199,6 +212,10 @@ public class DefaultFileLockManager implements FileLockManager {
             }
         }
 
+        public LockMode getMode() {
+            return mode;
+        }
+
         private java.nio.channels.FileLock lock(FileLockManager.LockMode lockMode) throws Throwable {
             LOGGER.debug("Waiting to acquire {} lock on {}.", lockMode.toString().toLowerCase(), displayName);
             long timeout = System.currentTimeMillis() + LOCK_TIMEOUT;
@@ -222,7 +239,7 @@ public class DefaultFileLockManager implements FileLockManager {
                                 throw new IllegalStateException(String.format("Unexpected lock protocol found in lock file '%s' for %s.", lockFile, displayName));
                             }
                             ownerPid = lockFileAccess.readUTF();
-                            ownerOperation= lockFileAccess.readUTF();
+                            ownerOperation = lockFileAccess.readUTF();
                         }
                     } finally {
                         informationRegionLock.release();
@@ -230,7 +247,7 @@ public class DefaultFileLockManager implements FileLockManager {
                 }
 
                 throw new LockTimeoutException(String.format("Timeout waiting to lock %s. It is currently in use by another Gradle instance.%nOwner PID: %s%nOur PID: %s%nOwner Operation: %s%nOur operation: %s%nLock file: %s",
-                        displayName, metaDataProvider.getProcessIdentifier(), ownerPid, ownerOperation, operationDisplayName, lockFile));
+                        displayName, ownerPid, metaDataProvider.getProcessIdentifier(), ownerOperation, operationDisplayName, lockFile));
             }
 
             try {
@@ -255,11 +272,12 @@ public class DefaultFileLockManager implements FileLockManager {
                     if (informationRegionLock == null) {
                         throw new IllegalStateException(String.format("Timeout waiting to lock the information region for lock %s", displayName));
                     }
+                    // check that the length of the reserved region is enough for storing our content
                     try {
                         lockFileAccess.seek(INFORMATION_REGION_POS);
                         lockFileAccess.writeByte(INFORMATION_REGION_PROTOCOL);
-                        lockFileAccess.writeUTF(metaDataProvider.getProcessIdentifier());
-                        lockFileAccess.writeUTF(operationDisplayName);
+                        lockFileAccess.writeUTF(trimIfNecessary(metaDataProvider.getProcessIdentifier()));
+                        lockFileAccess.writeUTF(trimIfNecessary(operationDisplayName));
                         lockFileAccess.setLength(lockFileAccess.getFilePointer());
                     } finally {
                         informationRegionLock.release();
@@ -274,12 +292,20 @@ public class DefaultFileLockManager implements FileLockManager {
             return stateRegionLock;
         }
 
+        private String trimIfNecessary(String inputString) {
+            if(inputString.length() > INFORMATION_REGION_DESCR_CHUNK_LIMIT){
+                return inputString.substring(0, INFORMATION_REGION_DESCR_CHUNK_LIMIT);
+            }else{
+                return inputString;
+            }
+        }
+
         private java.nio.channels.FileLock lockStateRegion(LockMode lockMode, long timeout) throws IOException, InterruptedException {
             return lockRegion(lockMode, timeout, STATE_REGION_POS, STATE_REGION_SIZE);
         }
 
         private java.nio.channels.FileLock lockInformationRegion(LockMode lockMode, long timeout) throws IOException, InterruptedException {
-            return lockRegion(lockMode, timeout, INFORMATION_REGION_POS, Long.MAX_VALUE - INFORMATION_REGION_POS);
+            return lockRegion(lockMode, timeout, INFORMATION_REGION_POS, INFORMATION_REGION_SIZE - INFORMATION_REGION_POS);
         }
 
         private java.nio.channels.FileLock lockRegion(FileLockManager.LockMode lockMode, long timeout, long start, long size) throws IOException, InterruptedException {
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java
index a9da3f0..d814342 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCache.java
@@ -17,6 +17,8 @@ package org.gradle.cache.internal;
 
 import org.gradle.CacheUsage;
 import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.cache.CacheOpenException;
 import org.gradle.cache.CacheValidator;
 import org.gradle.cache.PersistentCache;
 import org.gradle.util.GFileUtils;
@@ -31,13 +33,14 @@ import java.util.Properties;
 
 import static org.gradle.cache.internal.FileLockManager.LockMode;
 
-public class DefaultPersistentDirectoryCache extends DefaultPersistentDirectoryStore {
+public class DefaultPersistentDirectoryCache extends DefaultPersistentDirectoryStore implements ReferencablePersistentCache {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPersistentDirectoryCache.class);
     private final File propertiesFile;
     private final Properties properties = new Properties();
     private final CacheUsage cacheUsage;
     private final Action<? super PersistentCache> initAction;
     private final CacheValidator validator;
+    private boolean didRebuild;
 
     public DefaultPersistentDirectoryCache(File dir, String displayName, CacheUsage cacheUsage, CacheValidator validator, Map<String, ?> properties, LockMode lockMode, Action<? super PersistentCache> initAction, FileLockManager lockManager) {
         super(dir, displayName, lockMode, lockManager);
@@ -59,13 +62,30 @@ public class DefaultPersistentDirectoryCache extends DefaultPersistentDirectoryS
 
     protected void init() throws IOException {
         boolean valid = determineIfCacheIsValid(getLock());
-        if (!valid) {
-            // Escalate to exclusive lock and rebuild the cache
-            getLock().writeToFile(new Runnable() {
-                public void run() {
-                    buildCacheDir(initAction, getLock());
+        int tries = 3;
+        while (!valid) {
+            if (--tries < 0) {
+                throw new CacheOpenException(String.format("Failed to init valid cache for %s", this));
+            }
+            withExclusiveLock(new Action<FileLock>() {
+                public void execute(final FileLock fileLock) {
+                    boolean exclusiveLockValid;
+                    try {
+                        exclusiveLockValid = determineIfCacheIsValid(fileLock);
+                    } catch (IOException e) {
+                        throw new UncheckedIOException(e);
+                    }
+
+                    if (!exclusiveLockValid) {
+                        fileLock.writeFile(new Runnable() {
+                            public void run() {
+                                buildCacheDir(initAction, fileLock);
+                            }
+                        });
+                    }
                 }
             });
+            valid = determineIfCacheIsValid(getLock());
         }
     }
 
@@ -80,16 +100,20 @@ public class DefaultPersistentDirectoryCache extends DefaultPersistentDirectoryS
             initAction.execute(this);
         }
         GUtil.saveProperties(properties, propertiesFile);
+        didRebuild = true;
     }
 
-    private boolean determineIfCacheIsValid(FileLock lock) throws IOException {
-        if (cacheUsage != CacheUsage.ON) {
-            LOGGER.debug("Invalidating {} as cache usage is set to rebuild.", this);
-            return false;
-        }
-        if (validator!=null && !validator.isValid()) {
-            LOGGER.debug("Invalidating {} as cache validator return false.", this);
-            return false;
+    // made protected for DefaultPersistentDirectoryCacheTest.exceptionThrownIfValidCacheCannotBeInitd
+    protected boolean determineIfCacheIsValid(FileLock lock) throws IOException {
+        if (!didRebuild) {
+            if (cacheUsage != CacheUsage.ON) {
+                LOGGER.debug("Invalidating {} as cache usage is set to rebuild.", this);
+                return false;
+            }
+            if (validator!=null && !validator.isValid()) {
+                LOGGER.debug("Invalidating {} as cache validator return false.", this);
+                return false;
+            }
         }
 
         if (!lock.getUnlockedCleanly()) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
index e516256..7c83272 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryStore.java
@@ -15,16 +15,15 @@
  */
 package org.gradle.cache.internal;
 
+import org.gradle.api.Action;
+import org.gradle.cache.*;
 import org.gradle.internal.Factory;
-import org.gradle.cache.CacheOpenException;
-import org.gradle.cache.PersistentCache;
-import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.File;
 import java.io.IOException;
 
-public class DefaultPersistentDirectoryStore implements PersistentCache {
+public class DefaultPersistentDirectoryStore implements ReferencablePersistentCache {
     private final File dir;
     private final FileLockManager.LockMode lockMode;
     private final FileLockManager lockManager;
@@ -40,13 +39,15 @@ public class DefaultPersistentDirectoryStore implements PersistentCache {
 
     public DefaultPersistentDirectoryStore open() {
         dir.mkdirs();
-        cacheAccess = new DefaultCacheAccess(displayName, getLockTarget(), lockManager);
+        cacheAccess = createCacheAccess();
         try {
             cacheAccess.open(lockMode);
             try {
                 init();
             } catch (Throwable throwable) {
-                cacheAccess.close();
+                if (cacheAccess != null) {
+                    cacheAccess.close();
+                }
                 throw throwable;
             }
         } catch (Throwable e) {
@@ -56,6 +57,31 @@ public class DefaultPersistentDirectoryStore implements PersistentCache {
         return this;
     }
 
+    private DefaultCacheAccess createCacheAccess() {
+        return new DefaultCacheAccess(displayName, getLockTarget(), lockManager);
+    }
+
+    protected void withExclusiveLock(Action<FileLock> action) {
+        if (cacheAccess != null && (cacheAccess.getFileLock().getMode() == FileLockManager.LockMode.Exclusive)) {
+            action.execute(getLock());
+        } else {
+            boolean reopen = cacheAccess != null;
+            close();
+            DefaultCacheAccess exclusiveAccess = createCacheAccess();
+            exclusiveAccess.open(FileLockManager.LockMode.Exclusive);
+            try {
+                action.execute(exclusiveAccess.getFileLock());
+            } finally {
+                exclusiveAccess.close();
+            }
+            if (reopen) {
+                cacheAccess = createCacheAccess();
+                cacheAccess.open(lockMode);
+            }
+        }
+    }
+
+
     protected File getLockTarget() {
         return dir;
     }
@@ -74,7 +100,7 @@ public class DefaultPersistentDirectoryStore implements PersistentCache {
 
     }
 
-    protected FileLock getLock() {
+    public FileLock getLock() {
         return cacheAccess.getFileLock();
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
new file mode 100644
index 0000000..38b8acf
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCache.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2012 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 org.gradle.cache.internal;
+
+import org.gradle.cache.CacheOpenException;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.internal.Factory;
+import org.gradle.messaging.serialize.Serializer;
+
+import java.io.File;
+
+public class DelegateOnDemandPersistentDirectoryCache implements ReferencablePersistentCache {
+    private DefaultPersistentDirectoryCache delegateCache;
+    private boolean isOpen;
+
+    public DelegateOnDemandPersistentDirectoryCache(DefaultPersistentDirectoryCache cache) {
+        this.delegateCache = cache;
+    }
+
+    public DelegateOnDemandPersistentDirectoryCache open() {
+        this.isOpen = true;
+        return this;
+    }
+
+    public void close() {
+        this.isOpen = false;
+        delegateCache.close();
+    }
+
+    public FileLock getLock() {
+        return delegateCache.getLock();
+    }
+
+    public <T> T useCache(final String operationDisplayName, final Factory<? extends T> action) {
+        return runWithOpenedCache(new Factory<T>() {
+            public T create() {
+                return delegateCache.useCache(operationDisplayName, action);
+            }
+        });
+    }
+
+    public void useCache(final String operationDisplayName, final Runnable action) {
+        runWithOpenedCache(new Factory<Void>() {
+            public Void create() {
+                delegateCache.useCache(operationDisplayName, action);
+                return null;
+            }
+        });
+    }
+
+    public <T> T longRunningOperation(final String operationDisplayName, final Factory<? extends T> action) {
+        return runWithOpenedCache(new Factory<T>() {
+            public T create() {
+                return delegateCache.longRunningOperation(operationDisplayName, action);
+            }
+        });
+    }
+
+    public void longRunningOperation(final String operationDisplayName, final Runnable action) {
+        runWithOpenedCache(new Factory<Void>() {
+            public Void create() {
+                delegateCache.longRunningOperation(operationDisplayName, action);
+                return null;
+            }
+        });
+    }
+
+    private <T> T runWithOpenedCache(Factory<T> factory) {
+        if (isOpen) {
+            delegateCache.open();
+            try {
+                return factory.create();
+            } finally {
+                delegateCache.close();
+            }
+        } else {
+            throw new CacheOpenException("Cannot run operation on not opened cache");
+        }
+    }
+
+    public File getBaseDir() {
+        return delegateCache.getBaseDir();
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Class<V> valueType) {
+        throw new UnsupportedOperationException();
+    }
+
+    public <K, V> PersistentIndexedCache<K, V> createCache(File cacheFile, Class<K> keyType, Serializer<V> valueSerializer) {
+        throw new UnsupportedOperationException();
+    }
+
+    public String toString(){
+        return String.format("Delegate On Demand Cache for %s", delegateCache.toString());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java
index f36ca7d..892941b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileAccess.java
@@ -26,34 +26,44 @@ public interface FileAccess {
     /**
      * Runs the given action under a shared or exclusive lock on the target file.
      *
-     * <p>If an exclusive or shared lock is already held, the lock level is not changed and the action is executed. If no lock is already held,
-     * a shared lock is acquired, the action executed, and the lock released. This method blocks until the lock can be acquired.
-     *
      * @throws LockTimeoutException On timeout acquiring lock, if required.
      * @throws IllegalStateException When this lock has been closed.
+     * @throws FileIntegrityViolationException If the integrity of the file cannot be guaranteed (i.e. {@link #writeFile(Runnable)} has never been called)
+     * @throws InsufficientLockModeException If the held lock is not at least a shared lock (e.g. LockMode.NONE)
      */
-    <T> T readFromFile(Callable<? extends T> action) throws LockTimeoutException;
+    <T> T readFile(Callable<? extends T> action) throws LockTimeoutException, FileIntegrityViolationException, InsufficientLockModeException;
 
     /**
      * Runs the given action under a shared or exclusive lock on the target file.
      *
-     * <p>If an exclusive or shared lock is already held, the lock level is not changed and the action is executed. If no lock is already held,
-     * a shared lock is acquired, the action executed, and the lock released. This method blocks until the lock can be acquired.
-     *
      * @throws LockTimeoutException On timeout acquiring lock, if required.
      * @throws IllegalStateException When this lock has been closed.
+     * @throws FileIntegrityViolationException If the integrity of the file cannot be guaranteed (i.e. {@link #writeFile(Runnable)} has never been called)
+     * @throws InsufficientLockModeException If the held lock is not at least a shared lock (e.g. LockMode.NONE)
      */
-    <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException;
+    <T> T readFile(Factory<? extends T> action) throws LockTimeoutException, FileIntegrityViolationException, InsufficientLockModeException;
 
     /**
      * Runs the given action under an exclusive lock on the target file. If the given action fails, the lock is marked as uncleanly unlocked.
      *
-     * <p>If an exclusive lock is already held, the lock level is not changed and the action is executed. If a shared lock is already held,
-     * the lock is escalated to an exclusive lock, and reverted back to a shared lock when the action completes. If no lock is already held, an
-     * exclusive lock is acquired, the action executed, and the lock released.
+     * @throws LockTimeoutException On timeout acquiring lock, if required.
+     * @throws IllegalStateException When this lock has been closed.
+     * @throws FileIntegrityViolationException If the integrity of the file cannot be guaranteed (i.e. {@link #writeFile(Runnable)} has never been called)
+     * @throws InsufficientLockModeException If the held lock is not an exclusive lock.
+     */
+    void updateFile(Runnable action) throws LockTimeoutException, FileIntegrityViolationException, InsufficientLockModeException;
+
+    /**
+     * Runs the given action under an exclusive lock on the target file, without checking it's integrity. If the given action fails, the lock is marked as uncleanly unlocked.
+     *
+     * <p>This method should be used when it is of no consequence if the target was not previously unlocked, e.g. the content is being replaced.
+     *
+     * <p>Besides not performing integrity checking, this method shares the locking semantics of {@link #updateFile(Runnable)}
      *
      * @throws LockTimeoutException On timeout acquiring lock, if required.
      * @throws IllegalStateException When this lock has been closed.
+     * @throws InsufficientLockModeException If the held lock is not an exclusive lock.
      */
-    void writeToFile(Runnable action) throws LockTimeoutException;
+    void writeFile(Runnable action) throws LockTimeoutException, InsufficientLockModeException;
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileIntegrityViolationException.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileIntegrityViolationException.java
new file mode 100644
index 0000000..0c2a38a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileIntegrityViolationException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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 org.gradle.cache.internal;
+
+/**
+ * Indicates that the integrity of a file has been violated or cannot be guaranteed.
+ */
+public class FileIntegrityViolationException extends RuntimeException {
+
+    public FileIntegrityViolationException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileIntegrityViolationSuppressingPersistentStateCacheDecorator.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileIntegrityViolationSuppressingPersistentStateCacheDecorator.java
new file mode 100644
index 0000000..e53267c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileIntegrityViolationSuppressingPersistentStateCacheDecorator.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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 org.gradle.cache.internal;
+
+import org.gradle.cache.PersistentStateCache;
+
+public class FileIntegrityViolationSuppressingPersistentStateCacheDecorator<T> implements PersistentStateCache<T> {
+
+    private final PersistentStateCache<T> delegate;
+
+    public FileIntegrityViolationSuppressingPersistentStateCacheDecorator(PersistentStateCache<T> delegate) {
+        this.delegate = delegate;
+    }
+
+    public T get() {
+        try {
+            return delegate.get();
+        } catch (FileIntegrityViolationException e) {
+            return null;
+        }
+    }
+
+    public void set(T newValue) {
+        delegate.set(newValue);
+    }
+
+    public void update(final UpdateAction<T> updateAction) {
+        try {
+            delegate.update(updateAction);
+        } catch (FileIntegrityViolationException e) {
+            T newValue = updateAction.update(null);
+            set(newValue);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java
index da81890..04896e6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/FileLock.java
@@ -20,8 +20,10 @@ import java.io.File;
 
 public interface FileLock extends Closeable, FileAccess {
     /**
-     * Returns true if the most recent {@link #writeToFile(Runnable)} by any process succeeded (ie a process did not crash while updating
-     * the target file). Returns false if {@link #writeToFile(Runnable)} has never been called for the target file.
+     * Returns true if the most recent mutation method ({@link #updateFile(Runnable)} or {@link #writeFile(Runnable)} attempted by any process succeeded
+     * (ie a process did not crash while updating the target file).
+     *
+     * Returns false if no mutation method has been called for the target file.
      */
     boolean getUnlockedCleanly();
 
@@ -34,4 +36,9 @@ public interface FileLock extends Closeable, FileAccess {
      * Closes this lock, releasing the lock and any resources associated with it.
      */
     void close();
+
+    /**
+     * The actual mode of the lock. May be different to what was requested.
+     */
+    FileLockManager.LockMode getMode();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/InsufficientLockModeException.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/InsufficientLockModeException.java
new file mode 100644
index 0000000..4131baa
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/InsufficientLockModeException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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 org.gradle.cache.internal;
+
+public class InsufficientLockModeException extends RuntimeException {
+    public InsufficientLockModeException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java
index 543fd65..8006d0e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCache.java
@@ -33,16 +33,22 @@ public class MultiProcessSafePersistentIndexedCache<K, V> implements PersistentI
 
     public V get(final K key) {
         final PersistentIndexedCache<K, V> cache = getCache();
-        return fileAccess.readFromFile(new Factory<V>() {
-            public V create() {
-                return cache.get(key);
-            }
-        });
+        try {
+            return fileAccess.readFile(new Factory<V>() {
+                public V create() {
+                    return cache.get(key);
+                }
+            });
+        } catch (FileIntegrityViolationException e) {
+            return null;
+        }
     }
 
     public void put(final K key, final V value) {
         final PersistentIndexedCache<K, V> cache = getCache();
-        fileAccess.writeToFile(new Runnable() {
+        // Use writeFile because the cache can internally recover from datafile
+        // corruption, so we don't care at this level if it's corrupt
+        fileAccess.writeFile(new Runnable() {
             public void run() {
                 cache.put(key, value);
             }
@@ -51,7 +57,9 @@ public class MultiProcessSafePersistentIndexedCache<K, V> implements PersistentI
 
     public void remove(final K key) {
         final PersistentIndexedCache<K, V> cache = getCache();
-        fileAccess.writeToFile(new Runnable() {
+        // Use writeFile because the cache can internally recover from datafile
+        // corruption, so we don't care at this level if it's corrupt
+        fileAccess.writeFile(new Runnable() {
             public void run() {
                 cache.remove(key);
             }
@@ -68,7 +76,7 @@ public class MultiProcessSafePersistentIndexedCache<K, V> implements PersistentI
     public void close() {
         if (cache != null) {
             try {
-                fileAccess.writeToFile(new Runnable() {
+                fileAccess.writeFile(new Runnable() {
                     public void run() {
                         cache.close();
                     }
@@ -81,7 +89,9 @@ public class MultiProcessSafePersistentIndexedCache<K, V> implements PersistentI
 
     private PersistentIndexedCache<K, V> getCache() {
         if (cache == null) {
-            fileAccess.writeToFile(new Runnable() {
+            // Use writeFile because the cache can internally recover from datafile
+            // corruption, so we don't care at this level if it's corrupt
+            fileAccess.writeFile(new Runnable() {
                 public void run() {
                     cache = factory.create();
                 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java
index e8ecb00..1e70c09 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/OnDemandFileAccess.java
@@ -30,19 +30,28 @@ public class OnDemandFileAccess extends AbstractFileAccess {
         this.manager = manager;
     }
 
-    public <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException {
+    public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException, FileIntegrityViolationException {
         FileLock lock = manager.lock(targetFile, FileLockManager.LockMode.Shared, displayName);
         try {
-            return lock.readFromFile(action);
+            return lock.readFile(action);
         } finally {
             lock.close();
         }
     }
 
-    public void writeToFile(Runnable action) throws LockTimeoutException {
+    public void updateFile(Runnable action) throws LockTimeoutException, FileIntegrityViolationException {
         FileLock lock = manager.lock(targetFile, FileLockManager.LockMode.Exclusive, displayName);
         try {
-            lock.writeToFile(action);
+            lock.updateFile(action);
+        } finally {
+            lock.close();
+        }
+    }
+
+    public void writeFile(Runnable action) throws LockTimeoutException {
+        FileLock lock = manager.lock(targetFile, FileLockManager.LockMode.Exclusive, displayName);
+        try {
+            lock.writeFile(action);
         } finally {
             lock.close();
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/ReferencablePersistentCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/ReferencablePersistentCache.java
new file mode 100644
index 0000000..7acb3f6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/ReferencablePersistentCache.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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 org.gradle.cache.internal;
+
+import org.gradle.cache.PersistentCache;
+
+public interface ReferencablePersistentCache extends PersistentCache{
+
+    void close();
+    FileLock getLock();
+    ReferencablePersistentCache open();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java
index e5eeda4..fcd415d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/SimpleStateCache.java
@@ -19,7 +19,7 @@ package org.gradle.cache.internal;
 import org.gradle.api.GradleException;
 import org.gradle.internal.Factory;
 import org.gradle.cache.PersistentStateCache;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.*;
 
@@ -35,7 +35,7 @@ public class SimpleStateCache<T> implements PersistentStateCache<T> {
     }
 
     public T get() {
-        return fileAccess.readFromFile(new Factory<T>() {
+        return fileAccess.readFile(new Factory<T>() {
             public T create() {
                 return deserialize();
             }
@@ -43,7 +43,7 @@ public class SimpleStateCache<T> implements PersistentStateCache<T> {
     }
 
     public void set(final T newValue) {
-        fileAccess.writeToFile(new Runnable() {
+        fileAccess.writeFile(new Runnable() {
             public void run() {
                 serialize(newValue);
             }
@@ -51,7 +51,7 @@ public class SimpleStateCache<T> implements PersistentStateCache<T> {
     }
 
     public void update(final UpdateAction<T> updateAction) {
-        fileAccess.writeToFile(new Runnable() {
+        fileAccess.updateFile(new Runnable() {
             public void run() {
                 T oldValue = deserialize();
                 T newValue = updateAction.update(oldValue);
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java
index a652eed..061f283 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCache.java
@@ -17,7 +17,7 @@ package org.gradle.cache.internal.btree;
 
 import org.gradle.api.UncheckedIOException;
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/LockingBlockStore.java b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/LockingBlockStore.java
index 000a1fd..eaebd50 100644
--- a/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/LockingBlockStore.java
+++ b/subprojects/core/src/main/groovy/org/gradle/cache/internal/btree/LockingBlockStore.java
@@ -31,7 +31,7 @@ public class LockingBlockStore implements BlockStore {
     public void open(final Runnable initAction, final BlockStore.Factory factory) {
         store.open(new Runnable() {
             public void run() {
-                fileAccess.writeToFile(initAction);
+                fileAccess.updateFile(initAction);
             }
         }, factory);
     }
@@ -41,7 +41,7 @@ public class LockingBlockStore implements BlockStore {
     }
 
     public void flush() {
-        fileAccess.writeToFile(new Runnable() {
+        fileAccess.updateFile(new Runnable() {
             public void run() {
                 store.flush();
             }
@@ -49,7 +49,7 @@ public class LockingBlockStore implements BlockStore {
     }
 
     public void clear() {
-        fileAccess.writeToFile(new Runnable() {
+        fileAccess.updateFile(new Runnable() {
             public void run() {
                 store.clear();
             }
@@ -61,7 +61,7 @@ public class LockingBlockStore implements BlockStore {
     }
 
     public <T extends BlockPayload> T read(final BlockPointer pos, final Class<T> payloadType) {
-        return fileAccess.readFromFile(new Callable<T>() {
+        return fileAccess.readFile(new Callable<T>() {
             public T call() throws Exception {
                 return store.read(pos, payloadType);
             }
@@ -69,7 +69,7 @@ public class LockingBlockStore implements BlockStore {
     }
 
     public <T extends BlockPayload> T readFirst(final Class<T> payloadType) {
-        return fileAccess.readFromFile(new Callable<T>() {
+        return fileAccess.readFile(new Callable<T>() {
             public T call() throws Exception {
                 return store.readFirst(payloadType);
             }
@@ -77,7 +77,7 @@ public class LockingBlockStore implements BlockStore {
     }
 
     public void write(final BlockPayload block) {
-        fileAccess.writeToFile(new Runnable() {
+        fileAccess.updateFile(new Runnable() {
             public void run() {
                 store.write(block);
             }
@@ -85,7 +85,7 @@ public class LockingBlockStore implements BlockStore {
     }
 
     public void remove(final BlockPayload block) {
-        fileAccess.writeToFile(new Runnable() {
+        fileAccess.updateFile(new Runnable() {
             public void run() {
                 store.remove(block);
             }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
index 3fc010e..8ee6e7c 100755
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
@@ -18,8 +18,8 @@ package org.gradle.configuration;
 
 import org.gradle.internal.Factory;
 import org.gradle.internal.service.DefaultServiceRegistry;
-import org.gradle.api.internal.artifacts.dsl.BuildScriptClasspathScriptTransformer;
-import org.gradle.api.internal.artifacts.dsl.BuildScriptTransformer;
+import org.gradle.groovy.scripts.internal.BuildScriptClasspathScriptTransformer;
+import org.gradle.groovy.scripts.internal.BuildScriptTransformer;
 import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
 import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.api.internal.initialization.ScriptHandlerInternal;
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy
deleted file mode 100755
index 22d7dad..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.groovy.scripts
-
-import org.gradle.internal.service.ServiceRegistry
-import org.gradle.api.internal.file.FileOperations
-import org.gradle.logging.StandardOutputCapture
-import org.gradle.api.internal.ProcessOperations
-
-/**
- * @author Hans Dockter
- *
- */
-abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations, ProcessOperations {
-    private StandardOutputCapture standardOutputCapture
-    private Object target
-
-    void init(Object target, ServiceRegistry services) {
-        standardOutputCapture = services.get(StandardOutputCapture.class)
-        this.target = target
-    }
-
-    def Object getScriptTarget() {
-        return target
-    }
-
-    def StandardOutputCapture getStandardOutputCapture() {
-        return standardOutputCapture
-    }
-
-    void setProperty(String property, newValue) {
-        if ("metaClass" == property) {
-            setMetaClass((MetaClass) newValue)
-        } else if ("scriptTarget" == property) {
-            target = newValue
-        } else {
-            target."$property" = newValue
-        }
-    }
-
-    def propertyMissing(String property) {
-        if ('out' == property) {
-            System.out
-        } else {
-            target."$property"
-        }
-    }
-
-    def getProperties() {
-        return target.getProperties()
-    }
-
-    def hasProperty(String property) {
-        target.hasProperty(property)
-    }
-
-    def methodMissing(String name, Object params) {
-        return target.invokeMethod(name, params)
-    }
-}
-
-
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.java
new file mode 100755
index 0000000..4db2849
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2010 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 org.gradle.groovy.scripts;
+
+import groovy.lang.MetaClass;
+import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.internal.DynamicObjectUtil;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.api.internal.file.FileOperations;
+import org.gradle.logging.StandardOutputCapture;
+import org.gradle.api.internal.ProcessOperations;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ *
+ */
+public abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations, ProcessOperations {
+    private StandardOutputCapture standardOutputCapture;
+    private Object target;
+    private DynamicObject dynamicTarget;
+
+    public void init(Object target, ServiceRegistry services) {
+        standardOutputCapture = services.get(StandardOutputCapture.class);
+        setScriptTarget(target);
+    }
+
+    public Object getScriptTarget() {
+        return target;
+    }
+
+    private void setScriptTarget(Object target) {
+        this.target = target;
+        this.dynamicTarget = DynamicObjectUtil.asDynamicObject(target);
+    }
+
+    public StandardOutputCapture getStandardOutputCapture() {
+        return standardOutputCapture;
+    }
+
+    public void setProperty(String property, Object newValue) {
+        if ("metaClass".equals(property)) {
+            setMetaClass((MetaClass) newValue);
+        } else if ("scriptTarget".equals(property)) {
+            setScriptTarget(newValue);
+        } else {
+            dynamicTarget.setProperty(property, newValue);
+        }
+    }
+
+    public Object propertyMissing(String property) {
+        if ("out".equals(property)) {
+            return System.out;
+        } else {
+            return dynamicTarget.getProperty(property);
+        }
+    }
+
+    public Map<String, ?> getProperties() {
+        return dynamicTarget.getProperties();
+    }
+
+    public boolean hasProperty(String property) {
+        return dynamicTarget.hasProperty(property);
+    }
+
+    public Object methodMissing(String name, Object params) {
+        return dynamicTarget.invokeMethod(name, (Object[])params);
+    }
+}
+
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy
deleted file mode 100755
index 127c53f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.groovy.scripts
-
-import org.gradle.api.PathValidation
-import org.gradle.api.Script
-import org.gradle.api.file.ConfigurableFileCollection
-import org.gradle.api.file.ConfigurableFileTree
-import org.gradle.api.file.CopySpec
-import org.gradle.api.file.FileTree
-import org.gradle.api.initialization.dsl.ScriptHandler
-import org.gradle.api.internal.ProcessOperations
-import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction
-import org.gradle.api.logging.Logger
-import org.gradle.api.logging.Logging
-import org.gradle.api.logging.LoggingManager
-import org.gradle.api.plugins.ObjectConfigurationAction
-import org.gradle.api.resources.ResourceHandler
-import org.gradle.api.tasks.WorkResult
-import org.gradle.configuration.ScriptPluginFactory
-import org.gradle.internal.nativeplatform.filesystem.FileSystems
-import org.gradle.internal.service.ServiceRegistry
-import org.gradle.process.ExecResult
-import org.gradle.util.ConfigureUtil
-import org.gradle.util.DeprecationLogger
-import org.gradle.api.internal.file.*
-
-abstract class DefaultScript extends BasicScript {
-    private static final Logger LOGGER = Logging.getLogger(Script.class)
-    private ServiceRegistry services
-    private FileOperations fileOperations
-    private ProcessOperations processOperations
-    private LoggingManager loggingManager
-
-    def void init(Object target, ServiceRegistry services) {
-        super.init(target, services);
-        this.services = services
-        loggingManager = services.get(LoggingManager.class)
-        if (target instanceof FileOperations) {
-            fileOperations = target
-        } else if (scriptSource.resource.file) {
-            fileOperations = new DefaultFileOperations(new BaseDirFileResolver(FileSystems.default, scriptSource.resource.file.parentFile), null, null)
-        } else {
-            fileOperations = new DefaultFileOperations(new IdentityFileResolver(), null, null)
-        }
-        processOperations = fileOperations
-    }
-
-    FileResolver getFileResolver() {
-        fileOperations.fileResolver
-    }
-
-    void apply(Closure closure) {
-        ObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(ScriptPluginFactory.class), scriptTarget)
-        ConfigureUtil.configure(closure, action)
-        action.execute()
-    }
-
-    void apply(Map options) {
-        ObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(ScriptPluginFactory.class), scriptTarget)
-        ConfigureUtil.configureByMap(options, action)
-        action.execute()
-    }
-
-    ScriptHandler getBuildscript() {
-        return services.get(ScriptHandler.class);
-    }
-
-    void buildscript(Closure configureClosure) {
-        ConfigureUtil.configure(configureClosure, getBuildscript())
-    }
-
-    File file(Object path) {
-        fileOperations.file(path)
-    }
-
-    File file(Object path, PathValidation validation) {
-        fileOperations.file(path, validation)
-    }
-
-    URI uri(Object path) {
-        fileOperations.uri(path)
-    }
-
-    ConfigurableFileCollection files(Object... paths) {
-        fileOperations.files(paths)
-    }
-
-    ConfigurableFileCollection files(Object paths, Closure configureClosure) {
-        fileOperations.files(paths, configureClosure)
-    }
-
-    String relativePath(Object path) {
-        fileOperations.relativePath(path)
-    }
-
-    ConfigurableFileTree fileTree(Object baseDir) {
-        fileOperations.fileTree((Object)baseDir)
-    }
-
-    ConfigurableFileTree fileTree(Map args) {
-        fileOperations.fileTree(args)
-    }
-
-    ConfigurableFileTree fileTree(Closure closure) {
-        DeprecationLogger.nagUserWith("fileTree(Closure) is a deprecated method. Use fileTree((Object){ baseDir }) to have the closure used as the file tree base directory");
-        fileOperations.fileTree(closure)
-    }
-
-    ConfigurableFileTree fileTree(Object baseDir, Closure configureClosure) {
-        fileOperations.fileTree(baseDir, configureClosure);
-    }
-
-    FileTree zipTree(Object zipPath) {
-        fileOperations.zipTree(zipPath)
-    }
-
-    FileTree tarTree(Object tarPath) {
-        fileOperations.tarTree(tarPath)
-    }
-
-    ResourceHandler getResources() {
-        fileOperations.resources
-    }
-
-    WorkResult copy(Closure closure) {
-        fileOperations.copy(closure)
-    }
-
-    CopySpec copySpec(Closure closure) {
-        fileOperations.copySpec(closure)
-    }
-
-    File mkdir(Object path) {
-        return fileOperations.mkdir(path);
-    }
-
-    boolean delete(Object... paths) {
-        return fileOperations.delete(paths);
-    }
-
-    ExecResult javaexec(Closure closure) {
-        return processOperations.javaexec(closure);
-    }
-
-    ExecResult exec(Closure closure) {
-        return processOperations.exec(closure);
-    }
-
-    LoggingManager getLogging() {
-        return loggingManager
-    }
-
-    public Logger getLogger() {
-        return LOGGER;
-    }
-
-    def String toString() {
-        return "script"
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.java
new file mode 100755
index 0000000..7da03d1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2010 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 org.gradle.groovy.scripts;
+
+import groovy.lang.Closure;
+import org.gradle.api.PathValidation;
+import org.gradle.api.Script;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.api.internal.ProcessOperations;
+import org.gradle.api.internal.file.*;
+import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.resources.ResourceHandler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.process.ExecResult;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.DeprecationLogger;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Map;
+
+public abstract class DefaultScript extends BasicScript {
+    private static final Logger LOGGER = Logging.getLogger(Script.class);
+    private ServiceRegistry services;
+    private FileOperations fileOperations;
+    private ProcessOperations processOperations;
+    private LoggingManager loggingManager;
+
+    public void init(Object target, ServiceRegistry services) {
+        super.init(target, services);
+        this.services = services;
+        loggingManager = services.get(LoggingManager.class);
+        if (target instanceof FileOperations) {
+            fileOperations = (FileOperations) target;
+        } else if (getScriptSource().getResource().getFile() != null) {
+            fileOperations = new DefaultFileOperations(
+                    new BaseDirFileResolver(FileSystems.getDefault(), getScriptSource().getResource().getFile().getParentFile()), null, null
+            );
+        } else {
+            fileOperations = new DefaultFileOperations(new IdentityFileResolver(), null, null);
+        }
+
+        processOperations = (ProcessOperations) fileOperations;
+    }
+
+    public FileResolver getFileResolver() {
+        return fileOperations.getFileResolver();
+    }
+
+    private DefaultObjectConfigurationAction createObjectConfigurationAction() {
+        return new DefaultObjectConfigurationAction(getFileResolver(), services.get(ScriptPluginFactory.class), getScriptTarget());
+    }
+
+    public void apply(Closure closure) {
+        DefaultObjectConfigurationAction action = createObjectConfigurationAction();
+        ConfigureUtil.configure(closure, action);
+        action.execute();
+    }
+
+    public void apply(Map options) {
+        DefaultObjectConfigurationAction action = createObjectConfigurationAction();
+        ConfigureUtil.configureByMap(options, action);
+        action.execute();
+    }
+
+    public ScriptHandler getBuildscript() {
+        return services.get(ScriptHandler.class);
+    }
+
+    public void buildscript(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getBuildscript());
+    }
+
+    public File file(Object path) {
+        return fileOperations.file(path);
+    }
+
+    public File file(Object path, PathValidation validation) {
+        return fileOperations.file(path, validation);
+    }
+
+    public URI uri(Object path) {
+        return fileOperations.uri(path);
+    }
+
+    public ConfigurableFileCollection files(Object... paths) {
+        return fileOperations.files(paths);
+    }
+
+    public ConfigurableFileCollection files(Object paths, Closure configureClosure) {
+        return fileOperations.files(paths, configureClosure);
+    }
+
+    public String relativePath(Object path) {
+        return fileOperations.relativePath(path);
+    }
+
+    public ConfigurableFileTree fileTree(Object baseDir) {
+        return fileOperations.fileTree(baseDir);
+    }
+
+    public ConfigurableFileTree fileTree(Map<String, ?> args) {
+        return fileOperations.fileTree(args);
+    }
+
+    public ConfigurableFileTree fileTree(Closure closure) {
+        DeprecationLogger.nagUserWith("fileTree(Closure) is a deprecated method. Use fileTree((Object){ baseDir }) to have the closure used as the file tree base directory");
+        //noinspection deprecation
+        return fileOperations.fileTree(closure);
+    }
+
+    public ConfigurableFileTree fileTree(Object baseDir, Closure configureClosure) {
+        return fileOperations.fileTree(baseDir, configureClosure);
+    }
+
+    public FileTree zipTree(Object zipPath) {
+        return fileOperations.zipTree(zipPath);
+    }
+
+    public FileTree tarTree(Object tarPath) {
+        return fileOperations.tarTree(tarPath);
+    }
+
+    public ResourceHandler getResources() {
+        return fileOperations.getResources();
+    }
+
+    public WorkResult copy(Closure closure) {
+        return fileOperations.copy(closure);
+    }
+
+    public CopySpec copySpec(Closure closure) {
+        return fileOperations.copySpec(closure);
+    }
+
+    public File mkdir(Object path) {
+        return fileOperations.mkdir(path);
+    }
+
+    public boolean delete(Object... paths) {
+        return fileOperations.delete(paths);
+    }
+
+    public ExecResult javaexec(Closure closure) {
+        return processOperations.javaexec(closure);
+    }
+
+    public ExecResult exec(Closure closure) {
+        return processOperations.exec(closure);
+    }
+
+    public LoggingManager getLogging() {
+        return loggingManager;
+    }
+
+    public Logger getLogger() {
+        return LOGGER;
+    }
+
+    public String toString() {
+        return "script";
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
index c9cae76..62de9c6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.groovy.scripts;
 
-import org.gradle.api.internal.DirectInstantiator;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.DirectInstantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.groovy.scripts.internal.ScriptClassCompiler;
 import org.gradle.groovy.scripts.internal.ScriptRunnerFactory;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AbstractScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AbstractScriptTransformer.java
new file mode 100644
index 0000000..99d94c8
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/AbstractScriptTransformer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2009 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 org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GroovyCodeVisitor;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.ast.expr.*;
+import org.codehaus.groovy.control.SourceUnit;
+import org.gradle.groovy.scripts.Transformer;
+
+public abstract class AbstractScriptTransformer extends CompilationUnit.SourceUnitOperation implements Transformer {
+    public void register(CompilationUnit compilationUnit) {
+        compilationUnit.addPhaseOperation(this, getPhase());
+    }
+
+    protected abstract int getPhase();
+
+    protected boolean isMethodOnThis(MethodCallExpression call, String name) {
+        boolean isTaskMethod = call.getMethod() instanceof ConstantExpression && call.getMethod().getText().equals(
+                name);
+        return isTaskMethod && targetIsThis(call);
+    }
+
+    protected boolean targetIsThis(MethodCallExpression call) {
+        Expression target = call.getObjectExpression();
+        return target instanceof VariableExpression && target.getText().equals("this");
+    }
+
+    protected void visitScriptCode(SourceUnit source, GroovyCodeVisitor transformer) {
+        source.getAST().getStatementBlock().visit(transformer);
+        for (Object method : source.getAST().getMethods()) {
+            MethodNode methodNode = (MethodNode) method;
+            methodNode.getCode().visit(transformer);
+        }
+    }
+
+    protected ClassNode getScriptClass(SourceUnit source) {
+        if (source.getAST().getStatementBlock().getStatements().isEmpty() && source.getAST().getMethods().isEmpty()) {
+            // There is no script class when there are no statements or methods declared in the script
+            return null;
+        }
+        return source.getAST().getClasses().get(0);
+    }
+
+    protected void removeMethod(ClassNode declaringClass, MethodNode methodNode) {
+        declaringClass.getMethods().remove(methodNode);
+        declaringClass.getDeclaredMethods(methodNode.getName()).clear();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptClasspathScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptClasspathScriptTransformer.java
new file mode 100644
index 0000000..09197d1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptClasspathScriptTransformer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2009 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 org.gradle.groovy.scripts.internal;
+
+/**
+ * An implementation of ClasspathScriptTransformer for use in build scripts.  This subclass defines the script method
+ * name to be buildscript {}.
+ */
+public class BuildScriptClasspathScriptTransformer extends ClasspathScriptTransformer {
+    private final String classpathClosureName;
+
+    public BuildScriptClasspathScriptTransformer(String classpathClosureName) {
+        this.classpathClosureName = classpathClosureName;
+    }
+
+    public String getId() {
+        return classpathClosureName;
+    }
+
+    protected String getScriptMethodName() {
+        return classpathClosureName;
+    }
+}
+
+
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptTransformer.java
new file mode 100644
index 0000000..f504f25
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/BuildScriptTransformer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2009 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 org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.control.CompilationUnit;
+import org.gradle.groovy.scripts.Transformer;
+
+public class BuildScriptTransformer implements Transformer {
+    private final BuildScriptClasspathScriptTransformer classpathScriptTransformer;
+
+    public BuildScriptTransformer(BuildScriptClasspathScriptTransformer transformer) {
+        classpathScriptTransformer = transformer;
+    }
+
+    public String getId() {
+        return "no_" + classpathScriptTransformer.getId();
+    }
+
+    public void register(CompilationUnit compilationUnit) {
+        classpathScriptTransformer.invert().register(compilationUnit);
+        new TaskDefinitionScriptTransformer().register(compilationUnit);
+        new FixMainScriptTransformer().register(compilationUnit); // TODO - remove this
+        new StatementLabelsScriptTransformer().register(compilationUnit);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ClasspathScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ClasspathScriptTransformer.java
new file mode 100644
index 0000000..efe2952
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/ClasspathScriptTransformer.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2009 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 org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ImportNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.Phases;
+import org.codehaus.groovy.control.SourceUnit;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.groovy.scripts.Transformer;
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * The classpath script transformer uses Groovy's AST support to implement a two-phase
+ * compilation of a script into a "class path script" and an "everything else script".
+ * The classpath script can then be executed and it's results taken into account (in
+ * particular, to update the classpath) before the remainder of the script is executed.
+ */
+public abstract class ClasspathScriptTransformer extends AbstractScriptTransformer {
+    protected abstract String getScriptMethodName();
+
+    protected int getPhase() {
+        return Phases.CONVERSION;
+    }
+
+    public void call(SourceUnit source) throws CompilationFailedException {
+        Spec<Statement> spec = isScriptBlock();
+        filterStatements(source, spec);
+
+        // Filter imported classes which are not available yet
+
+        Iterator<ImportNode> iter = source.getAST().getImports().iterator();
+        while (iter.hasNext()) {
+            ImportNode importedClass = iter.next();
+            if (!isVisible(source, importedClass.getClassName())) {
+                try {
+                    Field field = ModuleNode.class.getDeclaredField("imports");
+                    field.setAccessible(true);
+                    Map value = (Map) field.get(source.getAST());
+                    value.remove(importedClass.getAlias());
+                } catch (Exception e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        }
+
+        iter = source.getAST().getStaticImports().values().iterator();
+        while (iter.hasNext()) {
+            ImportNode importedClass = iter.next();
+            if (!isVisible(source, importedClass.getClassName())) {
+                iter.remove();
+            }
+        }
+
+        iter = source.getAST().getStaticStarImports().values().iterator();
+        while (iter.hasNext()) {
+            ImportNode importedClass = iter.next();
+            if (!isVisible(source, importedClass.getClassName())) {
+                iter.remove();
+            }
+        }
+
+        ClassNode scriptClass = getScriptClass(source);
+
+        // Remove all the classes other than the main class
+        Iterator<ClassNode> classes = source.getAST().getClasses().iterator();
+        while (classes.hasNext()) {
+            ClassNode classNode = classes.next();
+            if (classNode != scriptClass) {
+                classes.remove();
+            }
+        }
+
+        // Remove all the methods from the main class
+        if (scriptClass != null) {
+            for (MethodNode methodNode : new ArrayList<MethodNode>(scriptClass.getMethods())) {
+                if (!methodNode.getName().equals("run")) {
+                    removeMethod(scriptClass, methodNode);
+                }
+            }
+        }
+
+        source.getAST().getMethods().clear();
+    }
+
+    private boolean isVisible(SourceUnit source, String className) {
+        try {
+            source.getClassLoader().loadClass(className);
+            return true;
+        } catch (ClassNotFoundException e) {
+            return false;
+        }
+    }
+
+    private void filterStatements(SourceUnit source, Spec<Statement> spec) {
+        Iterator statementIterator = source.getAST().getStatementBlock().getStatements().iterator();
+        while (statementIterator.hasNext()) {
+            Statement statement = (Statement) statementIterator.next();
+            if (!spec.isSatisfiedBy(statement)) {
+                statementIterator.remove();
+            }
+        }
+    }
+
+    public Transformer invert() {
+        return new AbstractScriptTransformer() {
+            protected int getPhase() {
+                return Phases.CANONICALIZATION;
+            }
+
+            public String getId() {
+                return "no_" + ClasspathScriptTransformer.this.getId();
+            }
+
+            @Override
+            public void call(SourceUnit source) throws CompilationFailedException {
+                Spec<Statement> spec = Specs.not(isScriptBlock());
+                filterStatements(source, spec);
+            }
+        };
+    }
+
+    public Spec<Statement> isScriptBlock() {
+        return new Spec<Statement>() {
+            public boolean isSatisfiedBy(Statement statement) {
+                if (!(statement instanceof ExpressionStatement)) {
+                    return false;
+                }
+
+                ExpressionStatement expressionStatement = (ExpressionStatement) statement;
+                if (!(expressionStatement.getExpression() instanceof MethodCallExpression)) {
+                    return false;
+                }
+
+                MethodCallExpression methodCall = (MethodCallExpression) expressionStatement.getExpression();
+                if (!isMethodOnThis(methodCall, getScriptMethodName())) {
+                    return false;
+                }
+
+                if (!(methodCall.getArguments() instanceof ArgumentListExpression)) {
+                    return false;
+                }
+
+                ArgumentListExpression args = (ArgumentListExpression) methodCall.getArguments();
+                return args.getExpressions().size() == 1 && args.getExpression(0) instanceof ClosureExpression;
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
index aaa5e91..e0549a1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandler.java
@@ -172,6 +172,10 @@ public class DefaultScriptCompilationHandler implements ScriptCompilationHandler
                     classLoader);
             return urlClassLoader.loadClass(source.getClassName()).asSubclass(scriptBaseClass);
         } catch (Exception e) {
+            File expectedClassFile = new File(scriptCacheDir, source.getClassName()+".class");
+            if(!expectedClassFile.exists()){
+                throw new GradleException(String.format("Could not load compiled classes for %s from cache. Expected class file %s does not exist.", source.getDisplayName(), expectedClassFile.getAbsolutePath()), e);
+            }
             throw new GradleException(String.format("Could not load compiled classes for %s from cache.", source.getDisplayName()), e);
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FixMainScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FixMainScriptTransformer.java
new file mode 100755
index 0000000..84afa71
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/FixMainScriptTransformer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2009 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 org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.Phases;
+import org.codehaus.groovy.control.SourceUnit;
+
+/**
+ * Fixes problem where main { } inside a closure is resolved as a call to static method main(). Does this by removing
+ * the static method.
+ */
+public class FixMainScriptTransformer extends AbstractScriptTransformer {
+    public String getId() {
+        return "fixMain";
+    }
+
+    @Override
+    protected int getPhase() {
+        return Phases.CONVERSION;
+    }
+
+    @Override
+    public void call(SourceUnit source) throws CompilationFailedException {
+        ClassNode scriptClass = getScriptClass(source);
+        if (scriptClass == null) {
+            return;
+        }
+        for (MethodNode methodNode : scriptClass.getMethods()) {
+            if (methodNode.getName().equals("main")) {
+                removeMethod(scriptClass, methodNode);
+                break;
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsDeprecationLogger.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsDeprecationLogger.java
new file mode 100644
index 0000000..1a3148b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsDeprecationLogger.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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 org.gradle.groovy.scripts.internal;
+
+import org.gradle.util.DeprecationLogger;
+
+/**
+ * Used by {@link StatementLabelsScriptTransformer} which weaves calls to the log() method into build scripts.
+ */
+public class StatementLabelsDeprecationLogger {
+    public static void log(String label, String sample) {
+        DeprecationLogger.nagUserWith(String.format("Usage of statement labels in build scripts has been deprecated "
+                + "and will no longer be supported in the next version of Gradle. In case you tried to configure a property "
+                + "named '%s', replace ':' with '=' or ' '. Otherwise it will not have the desired effect. \n\n%s",
+                label, sample));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsScriptTransformer.java
new file mode 100644
index 0000000..58bf036
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/StatementLabelsScriptTransformer.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 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 org.gradle.groovy.scripts.internal;
+
+import com.google.common.collect.Lists;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.expr.*;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.Phases;
+import org.codehaus.groovy.control.SourceUnit;
+
+import java.util.List;
+
+public class StatementLabelsScriptTransformer extends AbstractScriptTransformer {
+    @Override
+    protected int getPhase() {
+        return Phases.CANONICALIZATION;
+    }
+
+    public String getId() {
+        return "labels";
+    }
+
+    @Override
+    public void call(final SourceUnit source) throws CompilationFailedException {
+        final List<Statement> logStats = Lists.newArrayList();
+
+        // currently we only look in script code; could extend this to build script classes
+        visitScriptCode(source, new ClassCodeVisitorSupport() {
+            @Override
+            protected SourceUnit getSourceUnit() {
+                return source;
+            }
+
+            @Override
+            protected void visitStatement(Statement statement) {
+                if (statement.getStatementLabel() != null) {
+                    // Because we aren't failing the build, the script will be cached and this transformer won't run the next time.
+                    // In order to make the deprecation warning stick, we have to weave the call to StatementLabelsDeprecationLogger
+                    // into the build script.
+                    String label = statement.getStatementLabel();
+                    String sample = source.getSample(statement.getLineNumber(), statement.getColumnNumber(), null);
+                    Expression logExpr = new StaticMethodCallExpression(ClassHelper.makeWithoutCaching(StatementLabelsDeprecationLogger.class), "log",
+                            new ArgumentListExpression(new ConstantExpression(label), new ConstantExpression(sample)));
+                    logStats.add(new ExpressionStatement(logExpr));
+                }
+            }
+        });
+
+        source.getAST().getStatementBlock().addStatements(logStats);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/TaskDefinitionScriptTransformer.java b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/TaskDefinitionScriptTransformer.java
new file mode 100644
index 0000000..9644361
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/groovy/scripts/internal/TaskDefinitionScriptTransformer.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2010 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 org.gradle.groovy.scripts.internal;
+
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.DynamicVariable;
+import org.codehaus.groovy.ast.expr.*;
+import org.codehaus.groovy.control.CompilationFailedException;
+import org.codehaus.groovy.control.Phases;
+import org.codehaus.groovy.control.SourceUnit;
+
+import java.util.Collections;
+import java.util.List;
+
+public class TaskDefinitionScriptTransformer extends AbstractScriptTransformer {
+    protected int getPhase() {
+        return Phases.CANONICALIZATION;
+    }
+
+    public String getId() {
+        return "tasks";
+    }
+
+    public void call(SourceUnit source) throws CompilationFailedException {
+        visitScriptCode(source, new TaskDefinitionTransformer());
+    }
+
+    private class TaskDefinitionTransformer extends CodeVisitorSupport {
+        @Override
+        public void visitMethodCallExpression(MethodCallExpression call) {
+            doVisitMethodCallExpression(call);
+            super.visitMethodCallExpression(call);
+        }
+
+        private void doVisitMethodCallExpression(MethodCallExpression call) {
+            if (!isInstanceMethod(call, "task")) {
+                return;
+            }
+
+            ArgumentListExpression args = (ArgumentListExpression) call.getArguments();
+            if (args.getExpressions().size() == 0 || args.getExpressions().size() > 3) {
+                return;
+            }
+
+            // Matches: task <arg>{1, 3}
+
+            if (args.getExpressions().size() > 1) {
+                if (args.getExpression(0) instanceof MapExpression && args.getExpression(1) instanceof VariableExpression) {
+                    // Matches: task <name-value-pairs>, <identifier>, <arg>?
+                    // Map to: task(<name-value-pairs>, '<identifier>', <arg>?)
+                    transformVariableExpression(call, 1);
+                } else if (args.getExpression(0) instanceof VariableExpression) {
+                    // Matches: task <identifier>, <arg>?
+                    transformVariableExpression(call, 0);
+                }
+                return;
+            }
+
+            // Matches: task <arg> or task(<arg>)
+
+            Expression arg = args.getExpression(0);
+            if (arg instanceof VariableExpression) {
+                // Matches: task <identifier> or task(<identifier>)
+                transformVariableExpression(call, 0);
+            } else if (arg instanceof BinaryExpression) {
+                // Matches: task <expression> <operator> <expression>
+                transformBinaryExpression(call, (BinaryExpression) arg);
+            } else if (arg instanceof MethodCallExpression) {
+                // Matches: task <method-call>
+                maybeTransformNestedMethodCall((MethodCallExpression) arg, call);
+            }
+        }
+
+        private void transformVariableExpression(MethodCallExpression call, int index) {
+            ArgumentListExpression args = (ArgumentListExpression) call.getArguments();
+            VariableExpression arg = (VariableExpression) args.getExpression(index);
+            if (!isDynamicVar(arg)) {
+                return;
+            }
+
+            // Matches: task args?, <identifier>, args? or task(args?, <identifier>, args?)
+            // Map to: task(args?, '<identifier>', args?)
+            String taskName = arg.getText();
+            call.setMethod(new ConstantExpression("task"));
+            args.getExpressions().set(index, new ConstantExpression(taskName));
+        }
+
+        private void transformBinaryExpression(MethodCallExpression call, BinaryExpression expression) {
+
+            // Matches: task <expression> <operator> <expression>
+
+            if (expression.getLeftExpression() instanceof VariableExpression || expression.getLeftExpression() instanceof GStringExpression || expression
+                    .getLeftExpression() instanceof ConstantExpression) {
+                // Matches: task <identifier> <operator> <expression> | task <string> <operator> <expression>
+                // Map to: passThrough(task('<identifier>') <operator> <expression>) | passThrough(task(<string>) <operator> <expression>)
+                call.setMethod(new ConstantExpression("passThrough"));
+                Expression argument;
+                if (expression.getLeftExpression() instanceof VariableExpression) {
+                    argument = new ConstantExpression(expression.getLeftExpression().getText());
+                } else {
+                    argument = expression.getLeftExpression();
+                }
+                expression.setLeftExpression(new MethodCallExpression(call.getObjectExpression(), "task", argument));
+            } else if (expression.getLeftExpression() instanceof MethodCallExpression) {
+                // Matches: task <method-call> <operator> <expression>
+                MethodCallExpression transformedCall = new MethodCallExpression(call.getObjectExpression(), "task", new ArgumentListExpression());
+                boolean transformed = maybeTransformNestedMethodCall((MethodCallExpression) expression.getLeftExpression(), transformedCall);
+                if (transformed) {
+                    // Matches: task <identifier> <arg-list> <operator> <expression>
+                    // Map to: passThrough(task('<identifier>', <arg-list>) <operator> <expression>)
+                    call.setMethod(new ConstantExpression("passThrough"));
+                    expression.setLeftExpression(transformedCall);
+                }
+            }
+        }
+
+        private boolean maybeTransformNestedMethodCall(MethodCallExpression nestedMethod, MethodCallExpression target) {
+            if (!(isTaskIdentifier(nestedMethod.getMethod()) && targetIsThis(nestedMethod))) {
+                return false;
+            }
+
+            // Matches: task <identifier> <arg-list> | task <string> <arg-list>
+            // Map to: task("<identifier>", <arg-list>) | task(<string>, <arg-list>)
+
+            Expression taskName = nestedMethod.getMethod();
+            Expression mapArg = null;
+            List<Expression> extraArgs = Collections.emptyList();
+
+            if (nestedMethod.getArguments() instanceof TupleExpression) {
+                TupleExpression nestedArgs = (TupleExpression) nestedMethod.getArguments();
+                if (nestedArgs.getExpressions().size() == 2 && nestedArgs.getExpression(0) instanceof MapExpression && nestedArgs.getExpression(1) instanceof ClosureExpression) {
+                    // Matches: task <identifier>(<options-map>) <closure>
+                    mapArg = nestedArgs.getExpression(0);
+                    extraArgs = nestedArgs.getExpressions().subList(1, nestedArgs.getExpressions().size());
+                } else if (nestedArgs.getExpressions().size() == 1 && nestedArgs.getExpression(0) instanceof ClosureExpression) {
+                    // Matches: task <identifier> <closure>
+                    extraArgs = nestedArgs.getExpressions();
+                } else if (nestedArgs.getExpressions().size() == 1 && nestedArgs.getExpression(0) instanceof NamedArgumentListExpression) {
+                    // Matches: task <identifier>(<options-map>)
+                    mapArg = nestedArgs.getExpression(0);
+                } else if (nestedArgs.getExpressions().size() != 0) {
+                    return false;
+                }
+            }
+
+            target.setMethod(new ConstantExpression("task"));
+            ArgumentListExpression args = (ArgumentListExpression) target.getArguments();
+            args.getExpressions().clear();
+            if (mapArg != null) {
+                args.addExpression(mapArg);
+            }
+            args.addExpression(taskName);
+            for (Expression extraArg : extraArgs) {
+                args.addExpression(extraArg);
+            }
+            return true;
+        }
+
+        private boolean isInstanceMethod(MethodCallExpression call, String name) {
+            boolean isTaskMethod = isMethodOnThis(call, name);
+            if (!isTaskMethod) {
+                return false;
+            }
+
+            return call.getArguments() instanceof ArgumentListExpression;
+        }
+
+        private boolean isTaskIdentifier(Expression expression) {
+            return expression instanceof ConstantExpression || expression instanceof GStringExpression;
+        }
+
+        private boolean isDynamicVar(Expression expression) {
+            if (!(expression instanceof VariableExpression)) {
+                return false;
+            }
+            VariableExpression variableExpression = (VariableExpression) expression;
+            return variableExpression.getAccessedVariable() instanceof DynamicVariable;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
index 0da1bcf..b0d5349 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
@@ -24,16 +24,21 @@ import org.gradle.api.internal.plugins.EmbeddableJavaProject;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.cache.CacheBuilder;
 import org.gradle.cache.CacheRepository;
-import org.gradle.cache.PersistentStateCache;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.internal.FileLockManager;
+import org.gradle.internal.Factory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.util.WrapUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.net.MalformedURLException;
+import java.io.IOException;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.util.*;
+import java.util.Collection;
+import java.util.Set;
 
 /**
  * @author Hans Dockter
@@ -54,51 +59,39 @@ public class BuildSourceBuilder {
     }
 
     public URLClassLoader buildAndCreateClassLoader(StartParameter startParameter) {
-        Set<File> classpath = createBuildSourceClasspath(startParameter);
-        Iterator<File> classpathIterator = classpath.iterator();
-        URL[] urls = new URL[classpath.size()];
-        for (int i = 0; i < urls.length; i++) {
-            try {
-                urls[i] = classpathIterator.next().toURI().toURL();
-            } catch (MalformedURLException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-        return new URLClassLoader(urls, classLoaderRegistry.getRootClassLoader());
+        ClassPath classpath = createBuildSourceClasspath(startParameter);
+        return new URLClassLoader(classpath.getAsURLArray(), classLoaderRegistry.getRootClassLoader());
     }
 
-    public Set<File> createBuildSourceClasspath(StartParameter startParameter) {
+    private ClassPath createBuildSourceClasspath(StartParameter startParameter) {
         assert startParameter.getCurrentDir() != null && startParameter.getBuildFile() == null;
 
         LOGGER.debug("Starting to build the build sources.");
         if (!startParameter.getCurrentDir().isDirectory()) {
             LOGGER.debug("Gradle source dir does not exist. We leave.");
-            return new HashSet<File>();
+            return new DefaultClassPath();
         }
         LOGGER.info("================================================" + " Start building buildSrc");
-        StartParameter startParameterArg = startParameter.newInstance();
-        startParameterArg.setProjectProperties(startParameter.getProjectProperties());
-        startParameterArg.setSearchUpwards(false);
-        startParameterArg.setProfile(startParameter.isProfile());
 
         // If we were not the most recent version of Gradle to build the buildSrc dir, then do a clean build
         // Otherwise, just to a regular build
-        PersistentStateCache<Boolean> stateCache = cacheRepository.stateCache(Boolean.class, "buildSrc").forObject(startParameter.getCurrentDir()).withVersionStrategy(CacheBuilder.VersionStrategy.SharedCacheInvalidateOnVersionChange).open();
-        boolean rebuild = stateCache.get() == null;
-
-        GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameterArg);
-        BuildSrcBuildListener listener = new BuildSrcBuildListener(rebuild);
-        gradleLauncher.addListener(listener);
-        gradleLauncher.run().rethrowFailure();
-
-        stateCache.set(true);
-
-        Set<File> buildSourceClasspath = new LinkedHashSet<File>();
-        buildSourceClasspath.addAll(listener.getRuntimeClasspath());
-        LOGGER.debug("Gradle source classpath is: {}", buildSourceClasspath);
-        LOGGER.info("================================================" + " Finished building buildSrc");
+        final PersistentCache buildSrcCache = cacheRepository.
+                cache("buildSrc").
+                withLockMode(FileLockManager.LockMode.None).
+                forObject(startParameter.getCurrentDir()).
+                withVersionStrategy(CacheBuilder.VersionStrategy.SharedCacheInvalidateOnVersionChange).
+                open();
+
+        GradleLauncher gradleLauncher = buildGradleLauncher(startParameter);
+        return buildSrcCache.useCache("rebuild buildSrc", new BuildSrcUpdateFactory(buildSrcCache, gradleLauncher));
+    }
 
-        return buildSourceClasspath;
+    private GradleLauncher buildGradleLauncher(StartParameter startParameter) {
+        final StartParameter startParameterArg = startParameter.newInstance();
+        startParameterArg.setProjectProperties(startParameter.getProjectProperties());
+        startParameterArg.setSearchUpwards(false);
+        startParameterArg.setProfile(startParameter.isProfile());
+        return gradleLauncherFactory.newInstance(startParameterArg);
     }
 
     static URL getDefaultScript() {
@@ -130,4 +123,33 @@ public class BuildSourceBuilder {
             return classpath;
         }
     }
+
+    private static class BuildSrcUpdateFactory implements Factory<DefaultClassPath> {
+        private final PersistentCache cache;
+        private final GradleLauncher gradleLauncher;
+
+        public BuildSrcUpdateFactory(PersistentCache cache, GradleLauncher gradleLauncher) {
+            this.cache = cache;
+            this.gradleLauncher = gradleLauncher;
+        }
+
+        public DefaultClassPath create() {
+            File markerFile = new File(cache.getBaseDir(), "built.bin");
+            final boolean rebuild = !markerFile.exists();
+
+            BuildSrcBuildListener listener = new BuildSrcBuildListener(rebuild);
+            gradleLauncher.addListener(listener);
+            gradleLauncher.run().rethrowFailure();
+
+            Collection<File> classpath = listener.getRuntimeClasspath();
+            LOGGER.debug("Gradle source classpath is: {}", classpath);
+            LOGGER.info("================================================" + " Finished building buildSrc");
+            try {
+                markerFile.createNewFile();
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+            return new DefaultClassPath(classpath);
+        }
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
index 214d8b7..7bac31a 100755
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderRegistry.java
@@ -17,12 +17,13 @@
 package org.gradle.initialization;
 
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.util.*;
 
 import java.io.File;
 import java.net.URLClassLoader;
-import java.util.Collections;
 
 public class DefaultClassLoaderRegistry implements ClassLoaderRegistry {
     private final FilteringClassLoader rootClassLoader;
@@ -34,7 +35,7 @@ public class DefaultClassLoaderRegistry implements ClassLoaderRegistry {
         File toolsJar = Jvm.current().getToolsJar();
         if (toolsJar != null) {
             final ClassLoader systemClassLoaderParent = ClassLoader.getSystemClassLoader().getParent();
-            ClasspathUtil.addUrl((URLClassLoader) systemClassLoaderParent, GFileUtils.toURLs(Collections.singleton(toolsJar)));
+            ClasspathUtil.addUrl((URLClassLoader) systemClassLoaderParent, new DefaultClassPath(toolsJar).getAsURLs());
         }
 
         ClassLoader runtimeClassLoader = getClass().getClassLoader();
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
index 79d4eaa..5bda2c0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
@@ -16,19 +16,17 @@
 package org.gradle.initialization;
 
 import org.gradle.CacheUsage;
+import org.gradle.RefreshOptions;
 import org.gradle.StartParameter;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.initialization.Settings;
 import org.gradle.api.internal.file.BaseDirFileResolver;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.cli.*;
-import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.logging.internal.LoggingCommandLineConverter;
-import org.gradle.util.DeprecationLogger;
 
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -124,17 +122,7 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
 
         if (options.hasOption(CACHE)) {
             try {
-                final CacheUsage cacheUsage =  DeprecationLogger.whileDisabled(new Factory<CacheUsage>() {
-                    public CacheUsage create() {
-                        return CacheUsage.fromString(options.option(CACHE).getValue());  //To change body of implemented methods use File | Settings | File Templates.
-                    }
-                });
-                DeprecationLogger.whileDisabled(new Factory<Void>() {
-                    public Void create() {
-                        startParameter.setCacheUsage(cacheUsage);
-                        return null;
-                    }
-                });
+                startParameter.setCacheUsage(CacheUsage.fromString(options.option(CACHE).getValue()));
             } catch (InvalidUserDataException e) {
                 throw new CommandLineArgumentException(e.getMessage());
             }
@@ -157,7 +145,7 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         }
 
         if (options.hasOption(NO_OPT)) {
-            startParameter.setRerunTasks(true);
+            startParameter.setNoOpt(true);
         }
 
         if (options.hasOption(RERUN_TASKS)) {
@@ -185,15 +173,7 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         }
 
         if (options.hasOption(REFRESH)) {
-            //as refreshoptions is deprecated redirect refresh dependencies to --refresh-dependencies
-            final List<String> refreshOptions = options.option(REFRESH).getValues();
-            for(String refreshOption : refreshOptions){
-                if(refreshOption.equalsIgnoreCase("dependencies")){
-                    startParameter.setRefreshDependencies(true);
-                }else{
-                    throw new CommandLineArgumentException(String.format("Unknown refresh option '%s' specified.", refreshOption));
-                }
-            }
+            startParameter.setRefreshOptions(RefreshOptions.fromCommandLineOptions(options.option(REFRESH).getValues()));
         }
 
         if (options.hasOption(REFRESH_DEPENDENCIES)) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
index 512ace9..8c27b1d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
@@ -19,7 +19,7 @@ package org.gradle.initialization;
 import org.gradle.*;
 import org.gradle.api.internal.ExceptionAnalyser;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.project.GlobalServicesRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.project.TopLevelBuildServiceRegistry;
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java
deleted file mode 100755
index 61d146e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.listener;
-
-import org.gradle.api.Transformer;
-import org.gradle.messaging.dispatch.AsyncDispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.dispatch.StoppableDispatch;
-
-import java.util.concurrent.Executor;
-
-/**
- * An {@code AsyncListenerBroadcast} is a {@code ListenerBroadcast} which dispatches events to listeners asynchronously
- * to the generation of the events. Events are delivered in the order generated, and ordering between listeners is
- * maintained.
- */
-public class AsyncListenerBroadcast<T> extends ListenerBroadcast<T> {
-    public AsyncListenerBroadcast(Class<T> type, final Executor executor) {
-        super(type, new Transformer<StoppableDispatch<MethodInvocation>, StoppableDispatch<MethodInvocation>>() {
-            public StoppableDispatch<MethodInvocation> transform(StoppableDispatch<MethodInvocation> original) {
-                return new AsyncDispatch<MethodInvocation>(executor, original);
-            }
-        });
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java b/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java
new file mode 100755
index 0000000..2492797
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/BroadcastDispatch.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2010 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 org.gradle.listener;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.dispatch.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class BroadcastDispatch<T> implements Dispatch<MethodInvocation> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BroadcastDispatch.class);
+    private final Class<T> type;
+    private final Map<Object, Dispatch<MethodInvocation>> handlers
+            = new LinkedHashMap<Object, Dispatch<MethodInvocation>>();
+
+    public BroadcastDispatch(Class<T> type) {
+        this.type = type;
+    }
+
+    public Class<T> getType() {
+        return type;
+    }
+
+    public void add(Dispatch<MethodInvocation> dispatch) {
+        handlers.put(dispatch, dispatch);
+    }
+
+    public void add(T listener) {
+        handlers.put(listener, new ReflectionDispatch(listener));
+    }
+
+    public void add(String methodName, Closure closure) {
+        assertIsMethod(methodName);
+        handlers.put(closure, new ClosureInvocationHandler(methodName, closure));
+    }
+
+    public void add(String methodName, Action<?> action) {
+        assertIsMethod(methodName);
+        handlers.put(action, new ActionInvocationHandler(methodName, action));
+    }
+
+    private void assertIsMethod(String methodName) {
+        for (Method method : type.getMethods()) {
+            if (method.getName().equals(methodName)) {
+                return;
+            }
+        }
+        throw new IllegalArgumentException(String.format("Method %s() not found for listener type %s.", methodName,
+                type.getSimpleName()));
+    }
+
+    public void remove(Object listener) {
+        handlers.remove(listener);
+    }
+
+    private String getErrorMessage() {
+        String typeDescription = type.getSimpleName().replaceAll("(\\p{Upper})", " $1").trim().toLowerCase();
+        return String.format("Failed to notify %s.", typeDescription);
+    }
+
+    public void dispatch(MethodInvocation invocation) {
+        try {
+            ExceptionTrackingFailureHandler tracker = new ExceptionTrackingFailureHandler(LOGGER);
+            for (Dispatch<MethodInvocation> handler : new ArrayList<Dispatch<MethodInvocation>>(handlers.values())) {
+                try {
+                    handler.dispatch(invocation);
+                } catch (UncheckedException e) {
+                    tracker.dispatchFailed(invocation, e.getCause());
+                } catch (Throwable t) {
+                    tracker.dispatchFailed(invocation, t);
+                }
+            }
+            tracker.stop();
+        } catch (DispatchException t) {
+            throw new ListenerNotificationException(getErrorMessage(), t.getCause());
+        }
+    }
+
+    private class ClosureInvocationHandler implements Dispatch<MethodInvocation> {
+        private final String methodName;
+        private final Closure closure;
+
+        public ClosureInvocationHandler(String methodName, Closure closure) {
+            this.methodName = methodName;
+            this.closure = closure;
+        }
+
+        public void dispatch(MethodInvocation message) {
+            if (message.getMethod().getName().equals(methodName)) {
+                Object[] parameters = message.getArguments();
+                if (closure.getMaximumNumberOfParameters() < parameters.length) {
+                    parameters = Arrays.asList(parameters).subList(0, closure.getMaximumNumberOfParameters()).toArray();
+                }
+                closure.call(parameters);
+            }
+        }
+    }
+
+    private class ActionInvocationHandler implements Dispatch<MethodInvocation> {
+        private final String methodName;
+        private final Action action;
+
+        public ActionInvocationHandler(String methodName, Action action) {
+            this.methodName = methodName;
+            this.action = action;
+        }
+
+        public void dispatch(MethodInvocation message) {
+            if (message.getMethod().getName().equals(methodName)) {
+                action.execute(message.getArguments()[0]);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java b/subprojects/core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java
index c54d983..548f3db 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java
@@ -17,7 +17,6 @@
 package org.gradle.listener;
 
 import groovy.lang.Closure;
-import org.gradle.messaging.dispatch.BroadcastDispatch;
 import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.messaging.dispatch.ReflectionDispatch;
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
index 27fe1b9..034c30c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
@@ -18,8 +18,9 @@ package org.gradle.listener;
 
 import groovy.lang.Closure;
 import org.gradle.api.Action;
-import org.gradle.api.Transformer;
-import org.gradle.messaging.dispatch.*;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
 
 /**
  * <p>Manages a set of listeners of type T. Provides an implementation of T which can be used to broadcast to all
@@ -30,25 +31,15 @@ import org.gradle.messaging.dispatch.*;
  *
  * @param <T> The listener type.
  */
-public class ListenerBroadcast<T> implements StoppableDispatch<MethodInvocation> {
+public class ListenerBroadcast<T> implements Dispatch<MethodInvocation> {
     private final ProxyDispatchAdapter<T> source;
     private final BroadcastDispatch<T> broadcast;
     private final Class<T> type;
-    private final StoppableDispatch<MethodInvocation> dispatch;
 
     public ListenerBroadcast(Class<T> type) {
-        this(type, new Transformer<StoppableDispatch<MethodInvocation>, StoppableDispatch<MethodInvocation>>() {
-            public StoppableDispatch<MethodInvocation> transform(StoppableDispatch<MethodInvocation> original) {
-                return original;
-            }
-        });
-    }
-
-    protected ListenerBroadcast(Class<T> type, Transformer<StoppableDispatch<MethodInvocation>, StoppableDispatch<MethodInvocation>> transformer) {
         this.type = type;
         broadcast = new BroadcastDispatch<T>(type);
-        dispatch = transformer.transform(broadcast);
-        source = new ProxyDispatchAdapter<T>(dispatch, type);
+        source = new ProxyDispatchAdapter<T>(broadcast, type);
     }
 
     /**
@@ -147,10 +138,6 @@ public class ListenerBroadcast<T> implements StoppableDispatch<MethodInvocation>
      * @param event The event
      */
     public void dispatch(MethodInvocation event) {
-        dispatch.dispatch(event);
-    }
-
-    public void stop() {
-        dispatch.stop();
+        broadcast.dispatch(event);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/ConsoleRenderer.java b/subprojects/core/src/main/groovy/org/gradle/logging/ConsoleRenderer.java
new file mode 100644
index 0000000..6a596ac
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/ConsoleRenderer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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 org.gradle.logging;
+
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * Renders information in a format suitable for logging to the console.
+ */
+public class ConsoleRenderer {
+    /**
+     * Renders a path name as a file URL that is likely recognized by consoles.
+     */
+    public String asClickableFileUrl(File path) {
+        // File.toURI().toString() leads to an URL like this on Mac: file:/reports/index.html
+        // This URL is not recognized by the Mac console (too few leading slashes). We solve
+        // this be creating an URI with an empty authority.
+        try {
+            return new URI("file", "", path.toURI().getPath(), null, null).toString();
+        } catch (URISyntaxException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
index 9f69bb4..92f8ad3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
@@ -19,14 +19,14 @@ package org.gradle.logging;
 import org.gradle.StartParameter;
 import org.gradle.cli.CommandLineConverter;
 import org.gradle.internal.Factory;
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.TrueTimeProvider;
 import org.gradle.internal.nativeplatform.NoOpTerminalDetector;
 import org.gradle.internal.nativeplatform.TerminalDetector;
 import org.gradle.internal.nativeplatform.jna.JnaBootPathConfigurer;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.logging.internal.*;
-import org.gradle.logging.internal.slf4j.Slf4jLoggingConfigurer;
-import org.gradle.util.TimeProvider;
-import org.gradle.util.TrueTimeProvider;
+import org.gradle.logging.internal.logback.LogbackLoggingConfigurer;
 
 /**
  * A {@link org.gradle.internal.service.ServiceRegistry} implementation which provides the logging services.
@@ -103,7 +103,7 @@ public class LoggingServiceRegistry extends DefaultServiceRegistry {
         if (!isEmbedded) {
             //we want to reset and manipulate java logging only if we own the process, e.g. we're *not* embedded
             DefaultLoggingConfigurer compositeConfigurer = new DefaultLoggingConfigurer(renderer);
-            compositeConfigurer.add(new Slf4jLoggingConfigurer(renderer));
+            compositeConfigurer.add(new LogbackLoggingConfigurer(renderer));
             compositeConfigurer.add(new JavaUtilLoggingConfigurer());
             return new DefaultLoggingManagerFactory(compositeConfigurer, renderer, getStdOutLoggingSystem(), getStdErrLoggingSystem());
         } else {
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactory.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactory.java
index e240cb5..6888cbc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactory.java
@@ -16,10 +16,10 @@
 
 package org.gradle.logging.internal;
 
+import org.gradle.internal.TimeProvider;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.util.GUtil;
-import org.gradle.util.TimeProvider;
 
 public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
     private final ProgressListener progressListener;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdErrLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdErrLoggingSystem.java
index 507b01b..91aff9e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdErrLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdErrLoggingSystem.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.logging.internal;
 
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.PrintStream;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdOutLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdOutLoggingSystem.java
index 089cf3d..65b1865 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdOutLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStdOutLoggingSystem.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.logging.internal;
 
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 import java.io.PrintStream;
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStyledTextOutputFactory.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStyledTextOutputFactory.java
index 40c902d..7e0065f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStyledTextOutputFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/DefaultStyledTextOutputFactory.java
@@ -16,9 +16,9 @@
 package org.gradle.logging.internal;
 
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.TimeProvider;
 import org.gradle.logging.StyledTextOutput;
 import org.gradle.logging.StyledTextOutputFactory;
-import org.gradle.util.TimeProvider;
 
 public class DefaultStyledTextOutputFactory extends AbstractStyledTextOutputFactory implements StyledTextOutputFactory {
     private final OutputEventListener outputEventListener;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutput.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutput.java
index 365285f..7e9d8bf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutput.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutput.java
@@ -18,8 +18,8 @@ package org.gradle.logging.internal;
 import org.gradle.api.Action;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.TimeProvider;
 import org.gradle.util.LineBufferingOutputStream;
-import org.gradle.util.TimeProvider;
 
 import java.io.IOException;
 import java.util.ArrayList;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/MarkerFilter.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/MarkerFilter.java
deleted file mode 100644
index 93d014c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/MarkerFilter.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.logging.internal;
-
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.core.filter.Filter;
-import ch.qos.logback.core.spi.FilterReply;
-import org.slf4j.Marker;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public class MarkerFilter extends Filter<ILoggingEvent> {
-    private final List<Marker> markers;
-
-    private FilterReply onMismatch = FilterReply.NEUTRAL;
-
-    public MarkerFilter(Marker... markers) {
-        this.markers = Arrays.asList(markers);
-    }
-
-    public MarkerFilter(FilterReply onMismatch, Marker... markers) {
-        this(markers);
-        this.onMismatch = onMismatch;
-    }
-
-    @Override
-    public FilterReply decide(ILoggingEvent loggingEvent) {
-        Marker marker = loggingEvent.getMarker();
-        if (marker != null) {
-            for (Marker candidate : markers) {
-                if (marker.contains(candidate)) {
-                    return FilterReply.ACCEPT;
-                }
-            }
-        }
-        return onMismatch;
-    }
-
-    public FilterReply getOnMismatch() {
-        return onMismatch;
-    }
-
-    public void setOnMismatch(FilterReply onMismatch) {
-        this.onMismatch = onMismatch;
-    }
-
-    public List getMarkers() {
-        return markers;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/PrintStreamLoggingSystem.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/PrintStreamLoggingSystem.java
index 1fb684a..dbafd35 100644
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/PrintStreamLoggingSystem.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/PrintStreamLoggingSystem.java
@@ -18,8 +18,8 @@ package org.gradle.logging.internal;
 import org.gradle.api.Action;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.internal.TimeProvider;
 import org.gradle.util.LinePerThreadBufferingOutputStream;
-import org.gradle.util.TimeProvider;
 
 import java.io.PrintStream;
 import java.util.concurrent.atomic.AtomicReference;
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetectorFactory.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetectorFactory.java
index 9897a9d..75468a4 100755
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetectorFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/TerminalDetectorFactory.java
@@ -37,7 +37,7 @@ public class TerminalDetectorFactory {
             jnaBootPathConfigurer.configure();
             return new NativeServices().get(TerminalDetector.class);
         } catch (NativeIntegrationUnavailableException e) {
-            LOGGER.info("Unable to initialise the native integration for current platform: " + OperatingSystem.current() + ". Details: " + e.getMessage());
+            LOGGER.debug("Unable to initialise the native integration for current platform: " + OperatingSystem.current() + ". Details: " + e.getMessage());
             return new NoOpTerminalDetector();
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogLevelConverter.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogLevelConverter.java
new file mode 100644
index 0000000..3e13524
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogLevelConverter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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 org.gradle.logging.internal.logback;
+
+import ch.qos.logback.classic.Level;
+import org.gradle.api.Nullable;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logging;
+import org.slf4j.Marker;
+
+public class LogLevelConverter {
+    /**
+     * Maps a Logback log level and optional marker to a Gradle log level.
+     * Returns null if there is no equivalent Gradle log level (such as for TRACE).
+     */
+    @Nullable
+    public static LogLevel toGradleLogLevel(Level level, @Nullable Marker marker) {
+        switch(level.toInt()) {
+            case Level.TRACE_INT:
+                return null;
+            case Level.DEBUG_INT:
+                return LogLevel.DEBUG;
+            case Level.INFO_INT:
+                if (marker == Logging.LIFECYCLE) {
+                    return LogLevel.LIFECYCLE;
+                }
+                if (marker == Logging.QUIET) {
+                    return LogLevel.QUIET;
+                }
+                return LogLevel.INFO;
+            case Level.WARN_INT:
+                return LogLevel.WARN;
+            case Level.ERROR_INT:
+                return LogLevel.ERROR;
+            default:
+                throw new IllegalArgumentException("Don't know how to map Logback log level '" + level + "' to a Gradle log level");
+        }
+    }
+
+    public static Level toLogbackLevel(LogLevel level) {
+        switch (level) {
+            case DEBUG:
+                return Level.DEBUG;
+            case INFO:
+            case LIFECYCLE:
+            case QUIET:
+                return Level.INFO;
+            case WARN:
+                return Level.WARN;
+            case ERROR:
+                return Level.ERROR;
+            default:
+                throw new IllegalArgumentException("Don't know how to map Gradle log level '" + level + "' to a Logback log level");
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurer.java
new file mode 100644
index 0000000..cea30a6
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurer.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2010 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 org.gradle.logging.internal.logback;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableProxy;
+import ch.qos.logback.classic.turbo.TurboFilter;
+import ch.qos.logback.core.AppenderBase;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.spi.FilterReply;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.UncheckedException;
+import org.gradle.logging.internal.*;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Marker;
+
+import java.io.PrintStream;
+
+/**
+ * A {@link org.gradle.logging.internal.LoggingConfigurer} implementation which configures Logback
+ * to route logging events to a {@link org.gradle.logging.internal.OutputEventListener}.
+ *
+ * @author Hans Dockter
+ */
+public class LogbackLoggingConfigurer implements LoggingConfigurer {
+    private final OutputEventListener outputEventListener;
+    private final PrintStream defaultStandardOut = System.out;
+
+    private LogLevel currentLevel;
+
+    public LogbackLoggingConfigurer(OutputEventListener outputListener) {
+        outputEventListener = outputListener;
+    }
+
+    public void configure(LogLevel logLevel) {
+        if (logLevel == currentLevel) {
+            return;
+        }
+
+        try {
+            doConfigure(logLevel);
+        } catch (Throwable e) {
+            doFailSafeConfiguration();
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    private void doConfigure(LogLevel logLevel) {
+        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME);
+
+        if (currentLevel == null) {
+            context.reset();
+            context.addTurboFilter(new GradleFilter());
+            context.getLogger("org.apache.http.wire").setLevel(Level.OFF);
+            GradleAppender appender = new GradleAppender();
+            appender.setContext(context);
+            appender.start();
+            rootLogger.addAppender(appender);
+        }
+
+        currentLevel = logLevel;
+        rootLogger.setLevel(LogLevelConverter.toLogbackLevel(logLevel));
+    }
+
+    private void doFailSafeConfiguration() {
+        // Not really fail-safe, just less likely to fail
+        final LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        context.reset();
+        Logger rootLogger = context.getLogger(Logger.ROOT_LOGGER_NAME);
+        rootLogger.setLevel(Level.INFO);
+
+        ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
+        rootLogger.addAppender(appender);
+        appender.setContext(context);
+        appender.setTarget("System.err");
+
+        PatternLayout layout = new PatternLayout();
+        appender.setLayout(layout);
+        layout.setPattern("%msg%n%ex");
+        layout.setContext(context);
+
+        layout.start();
+        appender.start();
+    }
+
+    private class GradleFilter extends TurboFilter {
+        @Override
+        public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
+            Level loggerLevel = logger.getEffectiveLevel();
+            if (loggerLevel == Level.INFO && (level == Level.INFO || level == Level.WARN)
+                    || level == Level.INFO && (loggerLevel == Level.INFO || loggerLevel == Level.WARN)) {
+                // Need to take into account Gradle's LIFECYCLE and QUIET markers. Whether those are set can only be determined
+                // for the global log level, but not for the logger's log level (at least not without walking the logger's
+                // hierarchy, which is something that Logback is designed to avoid for performance reasons).
+                // Hence we base our decision on the global log level.
+                LogLevel eventLevel = LogLevelConverter.toGradleLogLevel(level, marker);
+                return eventLevel.compareTo(currentLevel) >= 0 ? FilterReply.ACCEPT : FilterReply.DENY;
+            }
+
+            return level.isGreaterOrEqual(loggerLevel) ? FilterReply.ACCEPT : FilterReply.DENY;
+        }
+    }
+
+    private class GradleAppender extends AppenderBase<ILoggingEvent> {
+        @Override
+        protected void append(ILoggingEvent event) {
+            try {
+                ThrowableProxy throwableProxy = (ThrowableProxy) event.getThrowableProxy();
+                Throwable throwable = throwableProxy == null ? null : throwableProxy.getThrowable();
+                String message = event.getFormattedMessage();
+                LogLevel level = LogLevelConverter.toGradleLogLevel(event.getLevel(), event.getMarker());
+                outputEventListener.onOutput(new LogEvent(event.getTimeStamp(), event.getLoggerName(), level, message, throwable));
+            } catch (Throwable t) {
+                // fall back to standard out
+                t.printStackTrace(defaultStandardOut);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/SimpleLogbackLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/SimpleLogbackLoggingConfigurer.java
new file mode 100644
index 0000000..6b0adee
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/logging/internal/logback/SimpleLogbackLoggingConfigurer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 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 org.gradle.logging.internal.logback;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.nativeplatform.NoOpTerminalDetector;
+import org.gradle.logging.internal.LoggingConfigurer;
+import org.gradle.logging.internal.OutputEventRenderer;
+
+/**
+ * Simple Logback configurer meant to be used in embedded mode
+ * in 'safe' environment (e.g. own class loader).
+ *
+ * by Szczepan Faber, created at: 1/23/12
+ */
+public class SimpleLogbackLoggingConfigurer implements LoggingConfigurer {
+    private final OutputEventRenderer renderer = new OutputEventRenderer(new NoOpTerminalDetector());
+    private final LoggingConfigurer configurer = new LogbackLoggingConfigurer(renderer);
+
+    public SimpleLogbackLoggingConfigurer() {
+        renderer.addStandardOutputAndError();
+    }
+
+    public void configure(LogLevel logLevel) {
+        renderer.configure(logLevel);
+        configurer.configure(logLevel);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/SimpleSlf4jLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/SimpleSlf4jLoggingConfigurer.java
deleted file mode 100644
index 8cfbeeb..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/SimpleSlf4jLoggingConfigurer.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.logging.internal.slf4j;
-
-import org.gradle.api.logging.LogLevel;
-import org.gradle.internal.nativeplatform.NoOpTerminalDetector;
-import org.gradle.logging.internal.LoggingConfigurer;
-import org.gradle.logging.internal.OutputEventRenderer;
-
-/**
- * Simple configurer for slf4j, meant to be used in embedded mode,
- * in 'safe' environment (e.g. own classloader).
- * <p>
- * by Szczepan Faber, created at: 1/23/12
- */
-public class SimpleSlf4jLoggingConfigurer implements LoggingConfigurer {
-
-    public void configure(LogLevel logLevel) {
-        OutputEventRenderer renderer = new OutputEventRenderer(new NoOpTerminalDetector());
-        renderer.addStandardOutputAndError();
-        renderer.configure(logLevel);
-        new Slf4jLoggingConfigurer(renderer).configure(logLevel);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurer.java
deleted file mode 100644
index d2f0769..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurer.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.logging.internal.slf4j;
-
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.PatternLayout;
-import ch.qos.logback.classic.filter.LevelFilter;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.classic.spi.ThrowableProxy;
-import ch.qos.logback.core.AppenderBase;
-import ch.qos.logback.core.ConsoleAppender;
-import ch.qos.logback.core.filter.Filter;
-import ch.qos.logback.core.spi.FilterReply;
-import org.gradle.api.logging.LogLevel;
-import org.gradle.api.logging.Logging;
-import org.gradle.internal.UncheckedException;
-import org.gradle.logging.internal.LogEvent;
-import org.gradle.logging.internal.LoggingConfigurer;
-import org.gradle.logging.internal.MarkerFilter;
-import org.gradle.logging.internal.OutputEventListener;
-import org.slf4j.LoggerFactory;
-
-import java.io.PrintStream;
-
-/**
- * A {@link org.gradle.logging.internal.LoggingConfigurer} implementation which configures SLF4J to route logging
- * events to a {@link org.gradle.logging.internal.OutputEventListener}.
- *
- * @author Hans Dockter
- */
-public class Slf4jLoggingConfigurer implements LoggingConfigurer {
-    private final Appender appender;
-    private LogLevel currentLevel;
-    private final PrintStream defaultStdOut;
-
-    public Slf4jLoggingConfigurer(OutputEventListener outputListener) {
-        defaultStdOut = System.out;
-        appender = new Appender(outputListener);
-    }
-
-    public void configure(LogLevel logLevel) {
-        if (currentLevel == logLevel) {
-            return;
-        }
-        try {
-            doConfigure(logLevel);
-        } catch (Throwable e) {
-            doFailsafeConfiguration();
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    private void doFailsafeConfiguration() {
-        // Not really failsafe, just less likely to fail
-        final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-        lc.reset();
-
-        ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<ILoggingEvent>() {{
-            setContext(lc);
-            setTarget("System.err");
-            setLayout(new PatternLayout() {{
-                setPattern("%msg%n%ex");
-                setContext(lc);
-                start();
-            }});
-            start();
-        }};
-
-        ch.qos.logback.classic.Logger rootLogger = lc.getLogger("ROOT");
-        rootLogger.setLevel(Level.INFO);
-        rootLogger.addAppender(consoleAppender);
-    }
-
-    private void doConfigure(LogLevel logLevel) {
-        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-        ch.qos.logback.classic.Logger rootLogger;
-        if (currentLevel == null) {
-            lc.reset();
-            appender.setContext(lc);
-            rootLogger = lc.getLogger("ROOT");
-            rootLogger.addAppender(appender);
-        } else {
-            rootLogger = lc.getLogger("ROOT");
-        }
-
-        currentLevel = logLevel;
-        appender.stop();
-        appender.clearAllFilters();
-
-        switch (logLevel) {
-            case DEBUG:
-                rootLogger.setLevel(Level.DEBUG);
-                break;
-            case INFO:
-                rootLogger.setLevel(Level.INFO);
-                break;
-            case LIFECYCLE:
-                appender.addFilter(new MarkerFilter(Logging.QUIET, Logging.LIFECYCLE));
-                appender.addFilter(createLevelFilter(lc, Level.INFO, FilterReply.DENY, FilterReply.NEUTRAL));
-                rootLogger.setLevel(Level.INFO);
-                break;
-            case QUIET:
-                appender.addFilter(new MarkerFilter(Logging.QUIET));
-                appender.addFilter(createLevelFilter(lc, Level.INFO, FilterReply.DENY, FilterReply.NEUTRAL));
-                rootLogger.setLevel(Level.INFO);
-                break;
-            case WARN:
-                rootLogger.setLevel(Level.WARN);
-                break;
-            case ERROR:
-                rootLogger.setLevel(Level.ERROR);
-                break;
-            default:
-                throw new IllegalArgumentException();
-        }
-
-        lc.getLogger("org.apache.http.wire").setLevel(Level.OFF);
-
-        appender.start();
-    }
-
-    private Filter<ILoggingEvent> createLevelFilter(LoggerContext lc, Level level, FilterReply onMatch,
-                                                    FilterReply onMismatch) {
-        LevelFilter levelFilter = new LevelFilter();
-        levelFilter.setContext(lc);
-        levelFilter.setOnMatch(onMatch);
-        levelFilter.setOnMismatch(onMismatch);
-        levelFilter.setLevel(level);
-        levelFilter.start();
-        return levelFilter;
-    }
-
-    private class Appender extends AppenderBase<ILoggingEvent> {
-        private final OutputEventListener listener;
-
-        private Appender(OutputEventListener listener) {
-            this.listener = listener;
-        }
-
-        @Override
-        protected void append(ILoggingEvent event) {
-            try {
-                ThrowableProxy throwableProxy = (ThrowableProxy) event.getThrowableProxy();
-                Throwable throwable = throwableProxy == null ? null : throwableProxy.getThrowable();
-                String message = event.getFormattedMessage();
-                listener.onOutput(new LogEvent(event.getTimeStamp(), event.getLoggerName(), toLogLevel(event), message, throwable));
-            } catch (Throwable t) {
-                // Give up and try stdout
-                t.printStackTrace(defaultStdOut);
-            }
-        }
-
-        private LogLevel toLogLevel(ILoggingEvent event) {
-            switch (event.getLevel().toInt()) {
-                case Level.DEBUG_INT:
-                    return LogLevel.DEBUG;
-                case Level.INFO_INT:
-                    if (event.getMarker() == Logging.LIFECYCLE) {
-                        return LogLevel.LIFECYCLE;
-                    }
-                    if (event.getMarker() == Logging.QUIET) {
-                        return LogLevel.QUIET;
-                    }
-                    return LogLevel.INFO;
-                case Level.WARN_INT:
-                    return LogLevel.WARN;
-                case Level.ERROR_INT:
-                    return LogLevel.ERROR;
-            }
-            throw new IllegalArgumentException(String.format("Cannot map SLF4j Level %s to a LogLevel", event.getLevel()));
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/Actor.java b/subprojects/core/src/main/groovy/org/gradle/messaging/actor/Actor.java
deleted file mode 100644
index 38e10ed..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/Actor.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.actor;
-
-import org.gradle.internal.concurrent.ThreadSafe;
-import org.gradle.messaging.dispatch.DispatchException;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.dispatch.StoppableDispatch;
-
-/**
- * <p>An {@code Actor} dispatches method calls to a target object in a thread-safe manner. Methods are called either by
- * calling {@link org.gradle.messaging.dispatch.Dispatch#dispatch(Object)} on the actor, or using the proxy object
- * returned by {@link #getProxy(Class)}. Methods are delivered to the target object in the order they are called on the
- * actor, but are delivered to the target object by a single thread at a time. In this way, the target object does not need
- * to perform any synchronisation.</p>
- *
- * <p>An actor uses one of two modes to deliver method calls to the target object:</p>
- *
- * <ul>
- * <li>Non-blocking, or asynchronous, so that method dispatch does not block waiting for the method call to be delivered or executed.
- * In this mode, the method return value or exception is not delivered back to the dispatcher.
- * </li>
- *
- * <li>Blocking, or synchronous, so that method dispatch blocks until the method call has been delivered and executed. In this mode, the
- * method return value or exception is delivered back to the dispatcher.
- * </li>
- *
- * </ul>
- *
- * <p>All implementations of this interface must be thread-safe.</p>
- */
-public interface Actor extends StoppableDispatch<MethodInvocation>, ThreadSafe {
-    /**
-     * Creates a proxy which delivers method calls to the target object.
-     *
-     * @param type the type for the proxy.
-     * @return The proxy.
-     */
-    <T> T getProxy(Class<T> type);
-
-    /**
-     * Stops accepting new method calls, and blocks until all method calls have been executed by the target object.
-     *
-     * @throws DispatchException When there were any failures dispatching method calls to the target object.
-     */
-    void stop() throws DispatchException;
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java b/subprojects/core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java
deleted file mode 100644
index 0bfbe9d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.actor.internal;
-
-import org.gradle.api.logging.Logging;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.internal.concurrent.ThreadSafe;
-import org.gradle.messaging.actor.Actor;
-import org.gradle.messaging.actor.ActorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.dispatch.*;
-
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-/**
- * A basic {@link ActorFactory} implementation. Currently cannot support creating both a blocking and non-blocking actor for the same target object.
- */
-public class DefaultActorFactory implements ActorFactory, Stoppable {
-    private final Map<Object, NonBlockingActor> nonBlockingActors = new IdentityHashMap<Object, NonBlockingActor>();
-    private final Map<Object, BlockingActor> blockingActors = new IdentityHashMap<Object, BlockingActor>();
-    private final Object lock = new Object();
-    private final ExecutorFactory executorFactory;
-
-    public DefaultActorFactory(ExecutorFactory executorFactory) {
-        this.executorFactory = executorFactory;
-    }
-
-    /**
-     * Stops all actors.
-     */
-    public void stop() {
-        synchronized (lock) {
-            try {
-                new CompositeStoppable().add(nonBlockingActors.values()).add(blockingActors.values()).stop();
-            } finally {
-                nonBlockingActors.clear();
-            }
-        }
-    }
-
-    public Actor createActor(Object target) {
-        if (target instanceof NonBlockingActor) {
-            return (NonBlockingActor) target;
-        }
-        synchronized (lock) {
-            if (blockingActors.containsKey(target)) {
-                throw new UnsupportedOperationException("Cannot create a non-blocking and blocking actor for the same object. This is not implemented yet.");
-            }
-            NonBlockingActor actor = nonBlockingActors.get(target);
-            if (actor == null) {
-                actor = new NonBlockingActor(target);
-                nonBlockingActors.put(target, actor);
-            }
-            return actor;
-        }
-    }
-
-    public Actor createBlockingActor(Object target) {
-        synchronized (lock) {
-            if (nonBlockingActors.containsKey(target)) {
-                throw new UnsupportedOperationException("Cannot create a non-blocking and blocking actor for the same object. This is not implemented yet.");
-            }
-            BlockingActor actor = blockingActors.get(target);
-            if (actor == null) {
-                actor = new BlockingActor(target);
-                blockingActors.put(target, actor);
-            }
-            return actor;
-        }
-    }
-
-    private void stopped(NonBlockingActor actor) {
-        synchronized (lock) {
-            nonBlockingActors.values().remove(actor);
-        }
-    }
-
-    private void stopped(BlockingActor actor) {
-        synchronized (lock) {
-            blockingActors.values().remove(actor);
-        }
-    }
-
-    private class BlockingActor implements Actor {
-        private final Dispatch<MethodInvocation> dispatch;
-        private final Object lock = new Object();
-        private boolean stopped;
-
-        public BlockingActor(Object target) {
-            dispatch = new ReflectionDispatch(target);
-        }
-
-        public <T> T getProxy(Class<T> type) {
-            return new ProxyDispatchAdapter<T>(this, type, ThreadSafe.class).getSource();
-        }
-
-        public void stop() throws DispatchException {
-            synchronized (lock) {
-                stopped = true;
-            }
-            stopped(this);
-        }
-
-        public void dispatch(MethodInvocation message) {
-            synchronized (lock) {
-                if (stopped) {
-                    throw new IllegalStateException("This actor has been stopped.");
-                }
-                dispatch.dispatch(message);
-            }
-        }
-    }
-
-    private class NonBlockingActor implements Actor {
-        private final StoppableDispatch<MethodInvocation> dispatch;
-        private final StoppableExecutor executor;
-        private final ExceptionTrackingFailureHandler failureHandler;
-
-        public NonBlockingActor(Object targetObject) {
-            executor = executorFactory.create(String.format("Dispatch %s", targetObject));
-            failureHandler = new ExceptionTrackingFailureHandler(Logging.getLogger(NonBlockingActor.class));
-            dispatch = new AsyncDispatch<MethodInvocation>(executor,
-                    new FailureHandlingDispatch<MethodInvocation>(
-                            new ReflectionDispatch(targetObject),
-                            failureHandler));
-        }
-
-        public <T> T getProxy(Class<T> type) {
-            return new ProxyDispatchAdapter<T>(this, type, ThreadSafe.class).getSource();
-        }
-
-        public void stop() {
-            try {
-                new CompositeStoppable(dispatch, executor, failureHandler).stop();
-            } finally {
-                stopped(this);
-            }
-        }
-
-        public void dispatch(MethodInvocation message) {
-            dispatch.dispatch(message);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java
deleted file mode 100755
index bb0db81..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.concurrent;
-
-import org.gradle.internal.Stoppable;
-
-/**
- * A {@link Stoppable} object whose stop process can be performed asynchronously.
- */
-public interface AsyncStoppable extends Stoppable {
-    /**
-     * <p>Requests that this stoppable commence a graceful stop. Does not block. You should call {@link
-     * org.gradle.internal.Stoppable#stop} to wait for the stop process to complete.</p>
-     *
-     * <p>Generally, an {@code AsyncStoppable} should continue to complete existing work after this method has returned.
-     * It should, however, stop accepting new work.</p>
-     *
-     * <p>
-     * Requesting stopping does not guarantee the stoppable actually stops.
-     * Requesting stopping means preparing for stopping; stopping accepting new work.
-     * You have to call stop at some point anyway if your intention is to completely stop the stoppable.
-     */
-    void requestStop();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java
deleted file mode 100644
index f9c5ddc..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.concurrent;
-
-import org.gradle.api.logging.Logging;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.dispatch.DispatchException;
-import org.gradle.messaging.dispatch.ExceptionTrackingFailureHandler;
-
-import java.util.Set;
-import java.util.concurrent.*;
-import java.util.concurrent.atomic.AtomicLong;
-
-public class DefaultExecutorFactory implements ExecutorFactory, Stoppable {
-    private final Set<StoppableExecutorImpl> executors = new CopyOnWriteArraySet<StoppableExecutorImpl>();
-
-    public void stop() {
-        try {
-            new CompositeStoppable(executors).stop();
-        } finally {
-            executors.clear();
-        }
-    }
-
-    public StoppableExecutor create(String displayName) {
-        StoppableExecutorImpl executor = new StoppableExecutorImpl(createExecutor(displayName));
-        executors.add(executor);
-        return executor;
-    }
-
-    protected ExecutorService createExecutor(String displayName) {
-        return Executors.newCachedThreadPool(new ThreadFactoryImpl(displayName));
-    }
-
-    private class StoppableExecutorImpl implements StoppableExecutor {
-        private final ExecutorService executor;
-        private final ExceptionTrackingFailureHandler failureHandler;
-        private final ThreadLocal<Runnable> executing = new ThreadLocal<Runnable>();
-
-        public StoppableExecutorImpl(ExecutorService executor) {
-            this.executor = executor;
-            failureHandler = new ExceptionTrackingFailureHandler(Logging.getLogger(StoppableExecutorImpl.class));
-        }
-
-        public void execute(final Runnable command) {
-            executor.execute(new Runnable() {
-                public void run() {
-                    executing.set(command);
-                    try {
-                        command.run();
-                    } catch (Throwable throwable) {
-                        failureHandler.dispatchFailed(command, throwable);
-                    } finally {
-                        executing.set(null);
-                    }
-                }
-            });
-        }
-
-        public void requestStop() {
-            executor.shutdown();
-        }
-
-        public void stop() {
-            stop(Integer.MAX_VALUE, TimeUnit.SECONDS);
-        }
-
-        public void stop(int timeoutValue, TimeUnit timeoutUnits) throws IllegalStateException {
-            requestStop();
-            if (executing.get() != null) {
-                throw new IllegalStateException("Cannot stop this executor from an executor thread.");
-            }
-            try {
-                try {
-                    if (!executor.awaitTermination(timeoutValue, timeoutUnits)) {
-                        executor.shutdownNow();
-                        throw new IllegalStateException("Timeout waiting for concurrent jobs to complete.");
-                    }
-                } catch (InterruptedException e) {
-                    throw new UncheckedException(e);
-                }
-                try {
-                    failureHandler.stop();
-                } catch (DispatchException e) {
-                    throw UncheckedException.throwAsUncheckedException(e.getCause());
-                }
-            } finally {
-                executors.remove(this);
-            }
-        }
-    }
-
-    private static class ThreadFactoryImpl implements ThreadFactory {
-        private final AtomicLong counter = new AtomicLong();
-        private final String displayName;
-
-        public ThreadFactoryImpl(String displayName) {
-            this.displayName = displayName;
-        }
-
-        public Thread newThread(Runnable r) {
-            Thread thread = new Thread(r);
-            long count = counter.incrementAndGet();
-            if (count == 1) {
-                thread.setName(displayName);
-            } else {
-                thread.setName(String.format("%s Thread %s", displayName, count));
-            }
-            return thread;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/ExecutorFactory.java b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/ExecutorFactory.java
deleted file mode 100644
index 0cead7b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/ExecutorFactory.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.concurrent;
-
-public interface ExecutorFactory {
-    /**
-     * Creates an executor which can run multiple tasks concurrently. It is the caller's responsibility to stop the executor.
-     *
-     * @param displayName The display name for the this executor. Used for thread names, logging and error message.
-     * @return The executor.
-     */
-    StoppableExecutor create(String displayName);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/StoppableExecutor.java b/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/StoppableExecutor.java
deleted file mode 100644
index c095804..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/concurrent/StoppableExecutor.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.concurrent;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-public interface StoppableExecutor extends AsyncStoppable, Executor {
-    /**
-     * Stops accepting new jobs and blocks until all currently executing jobs have been completed.
-     */
-    void stop();
-
-    /**
-     * Stops accepting new jobs and blocks until all currently executing jobs have been completed. Once the given
-     * timeout has been reached, forcefully stops remaining jobs and throws an exception.
-     *
-     * @throws IllegalStateException on timeout.
-     */
-    void stop(int timeoutValue, TimeUnit timeoutUnits) throws IllegalStateException;
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java
deleted file mode 100755
index 53e75ce..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.dispatch;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-
-import java.util.LinkedList;
-import java.util.concurrent.Executor;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * <p>A {@link org.gradle.messaging.dispatch.Dispatch} implementation which delivers messages asynchronously. Calls to
- * {@link #dispatch} queue the message. Worker threads deliver the messages in the order they have been received to one
- * of a pool of delegate {@link org.gradle.messaging.dispatch.Dispatch} instances.</p>
- */
-public class AsyncDispatch<T> implements StoppableDispatch<T>, AsyncStoppable {
-    private enum State {
-        Init, Stopped
-    }
-
-    private static final int MAX_QUEUE_SIZE = 200;
-    private final Lock lock = new ReentrantLock();
-    private final Condition condition = lock.newCondition();
-    private final LinkedList<T> queue = new LinkedList<T>();
-    private final Executor executor;
-    private final int maxQueueSize;
-    private int dispatchers;
-    private State state;
-
-    public AsyncDispatch(Executor executor) {
-        this(executor, null, MAX_QUEUE_SIZE);
-    }
-
-    public AsyncDispatch(Executor executor, final Dispatch<? super T> dispatch) {
-        this(executor, dispatch, MAX_QUEUE_SIZE);
-    }
-
-    public AsyncDispatch(Executor executor, final Dispatch<? super T> dispatch, int maxQueueSize) {
-        this.executor = executor;
-        this.maxQueueSize = maxQueueSize;
-        state = State.Init;
-        if (dispatch != null) {
-            dispatchTo(dispatch);
-        }
-    }
-
-    /**
-     * Starts dispatching messages to the given handler. The handler does not need to be thread-safe.
-     */
-    public void dispatchTo(final Dispatch<? super T> dispatch) {
-        onDispatchThreadStart();
-        executor.execute(new Runnable() {
-            public void run() {
-                try {
-                    dispatchMessages(dispatch);
-                } finally {
-                    onDispatchThreadExit();
-                }
-            }
-        });
-    }
-
-    private void onDispatchThreadStart() {
-        lock.lock();
-        try {
-            if (state != State.Init) {
-                throw new IllegalStateException("This dispatch has been stopped.");
-            }
-            dispatchers++;
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void onDispatchThreadExit() {
-        lock.lock();
-        try {
-            dispatchers--;
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void setState(State state) {
-        this.state = state;
-        condition.signalAll();
-    }
-
-    private void dispatchMessages(Dispatch<? super T> dispatch) {
-        while (true) {
-            T message = null;
-            lock.lock();
-            try {
-                while (state != State.Stopped && queue.isEmpty()) {
-                    try {
-                        condition.await();
-                    } catch (InterruptedException e) {
-                        throw new UncheckedException(e);
-                    }
-                }
-                if (!queue.isEmpty()) {
-                    message = queue.remove();
-                    condition.signalAll();
-                }
-            } finally {
-                lock.unlock();
-            }
-
-            if (message == null) {
-                // Have been stopped and nothing to deliver
-                return;
-            }
-
-            dispatch.dispatch(message);
-        }
-    }
-
-    public void dispatch(final T message) {
-        lock.lock();
-        try {
-            while (state != State.Stopped && queue.size() >= maxQueueSize) {
-                try {
-                    condition.await();
-                } catch (InterruptedException e) {
-                    throw new UncheckedException(e);
-                }
-            }
-            if (state == State.Stopped) {
-                throw new IllegalStateException("Cannot dispatch message, as this message dispatch has been stopped. Message: " + message);
-            }
-            queue.add(message);
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Commences a shutdown of this dispatch.
-     */
-    public void requestStop() {
-        lock.lock();
-        try {
-            doRequestStop();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void doRequestStop() {
-        setState(State.Stopped);
-    }
-
-    /**
-     * Stops accepting new messages, and blocks until all queued messages have been dispatched.
-     */
-    public void stop() {
-        lock.lock();
-        try {
-            setState(State.Stopped);
-            while (dispatchers > 0) {
-                condition.await();
-            }
-
-            if (!queue.isEmpty()) {
-                throw new IllegalStateException(
-                        "Cannot wait for messages to be dispatched, as there are no dispatch threads running.");
-            }
-        } catch (InterruptedException e) {
-            throw new UncheckedException(e);
-        } finally {
-            lock.unlock();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java
deleted file mode 100644
index 5ee35d2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.dispatch;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * <p>Receives messages asynchronously. One or more {@link Receive} instances can use used as a source of messages. Messages are sent to a {@link Dispatch} </p>
- * 
- * <p>It is also possible to specify an <code>onReceiversExhausted</code> Runnable callback that will be run when all of the given receivers
- * have been exhausted of messages. However, the current implementation is flawed in that this may erroneously fire if the first receiver
- * is exhausted before the second starts.
- */
-public class AsyncReceive<T> implements AsyncStoppable {
-    private enum State {
-        Init, Stopping, Stopped
-    }
-
-    private final Lock lock = new ReentrantLock();
-    private final Condition condition = lock.newCondition();
-    private final Executor executor;
-    private final List<Dispatch<? super T>> dispatches = new ArrayList<Dispatch<? super T>>();
-    private final Runnable onReceiversExhausted;
-    private int receivers;
-    private State state = State.Init;
-
-    public AsyncReceive(Executor executor) {
-        this(executor, (Runnable)null);
-    }
-
-    public AsyncReceive(Executor executor, Runnable onReceiversExhausted) {
-        this.executor = executor;
-        this.onReceiversExhausted = onReceiversExhausted;
-    }
-
-    public AsyncReceive(Executor executor, final Dispatch<? super T> dispatch) {
-        this(executor, dispatch, null);
-    }
-
-    public AsyncReceive(Executor executor, final Dispatch<? super T> dispatch, Runnable onReceiversExhausted) {
-        this(executor, onReceiversExhausted);
-        dispatchTo(dispatch);
-    }
-
-    /**
-     * Starts dispatching to the given dispatch. The dispatch does not need be thread-safe.
-     */
-    public void dispatchTo(final Dispatch<? super T> dispatch) {
-        lock.lock();
-        try {
-            dispatches.add(dispatch);
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Starts receiving from the given receive. The receive does not need to be thread-safe.
-     */
-    public void receiveFrom(final Receive<? extends T> receive) {
-        onReceiveThreadStart();
-        executor.execute(new Runnable() {
-            public void run() {
-                try {
-                    receiveMessages(receive);
-                } finally {
-                    onReceiveThreadExit();
-                }
-            }
-        });
-    }
-
-    private void onReceiveThreadStart() {
-        lock.lock();
-        try {
-            if (state != State.Init) {
-                throw new IllegalStateException("This receiver has been stopped.");
-            }
-            receivers++;
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void onReceiveThreadExit() {
-        lock.lock();
-        try {
-            receivers--;
-            if (receivers == 0 && onReceiversExhausted != null) {
-                onReceiversExhausted.run();
-            }
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void receiveMessages(Receive<? extends T> receive) {
-        while (true) {
-            Dispatch<? super T> dispatch;
-            lock.lock();
-            try {
-                while (dispatches.isEmpty() && state == State.Init) {
-                    try {
-                        condition.await();
-                    } catch (InterruptedException e) {
-                        throw UncheckedException.throwAsUncheckedException(e);
-                    }
-                }
-                if (state != State.Init) {
-                    return;
-                }
-                dispatch = dispatches.remove(0);
-            } finally {
-                lock.unlock();
-            }
-
-            try {
-                T message = receive.receive();
-                if (message == null) {
-                    return;
-                }
-
-                dispatch.dispatch(message);
-            } finally {
-                lock.lock();
-                try {
-                    dispatches.add(dispatch);
-                    condition.signalAll();
-                } finally {
-                    lock.unlock();
-                }
-            }
-        }
-    }
-
-    private void setState(State state) {
-        this.state = state;
-        condition.signalAll();
-    }
-
-    /**
-     * Stops receiving new messages.
-     */
-    public void requestStop() {
-        lock.lock();
-        try {
-            doRequestStop();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void doRequestStop() {
-        if (receivers > 0) {
-            setState(State.Stopping);
-        } else {
-            setState(State.Stopped);
-        }
-    }
-
-    /**
-     * Stops receiving new messages. Blocks until all queued messages have been delivered.
-     */
-    public void stop() {
-        lock.lock();
-        try {
-            doRequestStop();
-
-            while (receivers > 0) {
-                condition.await();
-            }
-
-            setState(State.Stopped);
-        } catch (InterruptedException e) {
-            throw new UncheckedException(e);
-        } finally {
-            lock.unlock();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java
deleted file mode 100755
index d85b5e5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.dispatch;
-
-import groovy.lang.Closure;
-import org.gradle.api.Action;
-import org.gradle.internal.UncheckedException;
-import org.gradle.listener.ListenerNotificationException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-public class BroadcastDispatch<T> implements StoppableDispatch<MethodInvocation> {
-    private static final Logger LOGGER = LoggerFactory.getLogger(BroadcastDispatch.class);
-    private final Class<T> type;
-    private final Map<Object, Dispatch<MethodInvocation>> handlers
-            = new LinkedHashMap<Object, Dispatch<MethodInvocation>>();
-
-    public BroadcastDispatch(Class<T> type) {
-        this.type = type;
-    }
-
-    public Class<T> getType() {
-        return type;
-    }
-
-    public void add(Dispatch<MethodInvocation> dispatch) {
-        handlers.put(dispatch, dispatch);
-    }
-
-    public void add(T listener) {
-        handlers.put(listener, new ReflectionDispatch(listener));
-    }
-
-    public void add(String methodName, Closure closure) {
-        assertIsMethod(methodName);
-        handlers.put(closure, new ClosureInvocationHandler(methodName, closure));
-    }
-
-    public void add(String methodName, Action<?> action) {
-        assertIsMethod(methodName);
-        handlers.put(action, new ActionInvocationHandler(methodName, action));
-    }
-
-    private void assertIsMethod(String methodName) {
-        for (Method method : type.getMethods()) {
-            if (method.getName().equals(methodName)) {
-                return;
-            }
-        }
-        throw new IllegalArgumentException(String.format("Method %s() not found for listener type %s.", methodName,
-                type.getSimpleName()));
-    }
-
-    public void remove(Object listener) {
-        handlers.remove(listener);
-    }
-
-    private String getErrorMessage() {
-        String typeDescription = type.getSimpleName().replaceAll("(\\p{Upper})", " $1").trim().toLowerCase();
-        return String.format("Failed to notify %s.", typeDescription);
-    }
-
-    public void dispatch(MethodInvocation invocation) {
-        try {
-            ExceptionTrackingFailureHandler tracker = new ExceptionTrackingFailureHandler(LOGGER);
-            for (Dispatch<MethodInvocation> handler : new ArrayList<Dispatch<MethodInvocation>>(handlers.values())) {
-                try {
-                    handler.dispatch(invocation);
-                } catch (UncheckedException e) {
-                    tracker.dispatchFailed(invocation, e.getCause());
-                } catch (Throwable t) {
-                    tracker.dispatchFailed(invocation, t);
-                }
-            }
-            tracker.stop();
-        } catch (DispatchException t) {
-            throw new ListenerNotificationException(getErrorMessage(), t.getCause());
-        }
-    }
-
-    public void stop() {
-    }
-
-    private class ClosureInvocationHandler implements Dispatch<MethodInvocation> {
-        private final String methodName;
-        private final Closure closure;
-
-        public ClosureInvocationHandler(String methodName, Closure closure) {
-            this.methodName = methodName;
-            this.closure = closure;
-        }
-
-        public void dispatch(MethodInvocation message) {
-            if (message.getMethod().getName().equals(methodName)) {
-                Object[] parameters = message.getArguments();
-                if (closure.getMaximumNumberOfParameters() < parameters.length) {
-                    parameters = Arrays.asList(parameters).subList(0, closure.getMaximumNumberOfParameters()).toArray();
-                }
-                closure.call(parameters);
-            }
-        }
-    }
-
-    private class ActionInvocationHandler implements Dispatch<MethodInvocation> {
-        private final String methodName;
-        private final Action action;
-
-        public ActionInvocationHandler(String methodName, Action action) {
-            this.methodName = methodName;
-            this.action = action;
-        }
-
-        public void dispatch(MethodInvocation message) {
-            if (message.getMethod().getName().equals(methodName)) {
-                action.execute(message.getArguments()[0]);
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DelayedReceive.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DelayedReceive.java
deleted file mode 100644
index 6882305..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DelayedReceive.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.dispatch;
-
-import org.gradle.internal.Stoppable;
-import org.gradle.internal.UncheckedException;
-import org.gradle.util.TimeProvider;
-
-import java.util.Date;
-import java.util.Iterator;
-import java.util.PriorityQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class DelayedReceive<T> implements Stoppable, Receive<T> {
-    private final TimeProvider timeProvider;
-    private final Lock lock = new ReentrantLock();
-    private final Condition condition = lock.newCondition();
-    private final PriorityQueue<DelayedMessage> queue = new PriorityQueue<DelayedMessage>();
-    private boolean stopping;
-
-    public DelayedReceive(TimeProvider timeProvider) {
-        this.timeProvider = timeProvider;
-    }
-
-    public T receive() {
-        lock.lock();
-        try {
-            while (true) {
-                DelayedMessage message = queue.peek();
-                if (message == null && stopping) {
-                    return null;
-                }
-                if (message == null) {
-                    condition.await();
-                    continue;
-                }
-
-                long now = timeProvider.getCurrentTime();
-                if (message.dispatchTime > now) {
-                    condition.awaitUntil(new Date(message.dispatchTime));
-                } else {
-                    queue.poll();
-                    if (queue.isEmpty()) {
-                        condition.signalAll();
-                    }
-                    return message.message;
-                }
-            }
-        } catch (InterruptedException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Dispatches the given message after the given delay.
-     */
-    public void dispatchLater(T message, int delayValue, TimeUnit delayUnits) {
-        long dispatchTime = timeProvider.getCurrentTime() + delayUnits.toMillis(delayValue);
-        lock.lock();
-        try {
-            if (stopping) {
-                throw new IllegalStateException("This dispatch has been stopped.");
-            }
-            queue.add(new DelayedMessage(dispatchTime, message));
-            condition.signalAll();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Removes one instance of the given message from the queue.
-     *
-     * @return true if removed, false if not. If false is returned, the message may be currently being dispatched.
-     */
-    public boolean remove(T message) {
-        lock.lock();
-        try {
-            Iterator<DelayedMessage> iterator = queue.iterator();
-            while (iterator.hasNext()) {
-                DelayedMessage next = iterator.next();
-                if (next.message.equals(message)) {
-                    iterator.remove();
-                    return true;
-                }
-            }
-            return false;
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Removes all queued messages.
-     */
-    public void clear() {
-        lock.lock();
-        try {
-            queue.clear();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Blocks until all queued messages are delivered.
-     */
-    public void stop() {
-        lock.lock();
-        try {
-            stopping = true;
-            condition.signalAll();
-            while (!queue.isEmpty()) {
-                try {
-                    condition.await();
-                } catch (InterruptedException e) {
-                    throw UncheckedException.throwAsUncheckedException(e);
-                }
-            }
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private class DelayedMessage implements Comparable<DelayedMessage> {
-        private final long dispatchTime;
-        private final T message;
-
-        private DelayedMessage(long dispatchTime, T message) {
-            this.dispatchTime = dispatchTime;
-            this.message = message;
-        }
-
-        public int compareTo(DelayedMessage delayedMessage) {
-            if (dispatchTime > delayedMessage.dispatchTime) {
-                return 1;
-            } else if (dispatchTime < delayedMessage.dispatchTime) {
-                return -1;
-            }
-            return 0;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Dispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Dispatch.java
deleted file mode 100755
index ed71bf0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Dispatch.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.dispatch;
-
-/**
- * A sink for messages. Implementations do not have to be thread-safe.
- */
-public interface Dispatch<T> {
-    /**
-     * Dispatches the next message. Blocks until the messages has been accepted but generally does not wait for the
-     * message to be processed. Delivery guarantees are implementation specific.
-     *
-     * @param message The message.
-     */
-    void dispatch(T message);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java
deleted file mode 100755
index 9957149..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.dispatch;
-
-import org.gradle.internal.Stoppable;
-
-public interface StoppableDispatch<T> extends Dispatch<T>, Stoppable {
-    /**
-     * Stops this dispatch. Stops accepting new events and blocks until all events have been dispatched.
-     */
-    void stop();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java
deleted file mode 100755
index b0320df..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-
-/**
- * Manages a set of incoming and outgoing channels between 2 peers. Implementations must be thread-safe.
- */
-public interface ObjectConnection extends Addressable, AsyncStoppable {
-    /**
-     * Creates a transmitter for outgoing messages on the given type. The returned object is thread-safe.
-     *
-     * @param type The type
-     * @return A sink. Method calls made on this object are sent as outgoing messages.
-     */
-    <T> T addOutgoing(Class<T> type);
-
-    /**
-     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
-     * thread-safe. Messages are delivered to the handler by a single thread.
-     *
-     * @param type The type.
-     * @param instance The handler instance. Incoming messages on the given type are delivered to this handler.
-     */
-    <T> void addIncoming(Class<T> type, T instance);
-
-    /**
-     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
-     * thread-safe. Messages are delivered to the handler by a single thread.
-     *
-     * @param type The type.
-     * @param dispatch The handler instance. Incoming messages on the given type are delivered to this handler.
-     */
-    void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch);
-
-    /**
-     * Commences a graceful stop of this connection. Stops accepting outgoing messages. Requests that the peer stop
-     * sending incoming messages.
-     */
-    void requestStop();
-
-    /**
-     * Performs a graceful stop of this connection. Stops accepting outgoing message. Blocks until all incoming messages
-     * have been handled, and all outgoing messages have been handled by the peer.
-     */
-    void stop();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
deleted file mode 100644
index df0969f..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.dispatch.*;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Adapts a {@link Connection} into an {@link AsyncConnection}.
- */
-public class AsyncConnectionAdapter<T> implements AsyncConnection<T>, Stoppable {
-    private final Connection<T> connection;
-    private final AsyncReceive<T> incoming;
-    private final ProtocolStack<T> stack;
-    private final AsyncDispatch<T> outgoing;
-    private final Set<Stoppable> executors = new HashSet<Stoppable>();
-
-    public AsyncConnectionAdapter(Connection<T> connection, DispatchFailureHandler<? super T> dispatchFailureHandler, ExecutorFactory executor, Protocol<T>... protocols) {
-        this.connection = connection;
-
-        StoppableExecutor outgoingExecutor = executor.create(String.format("%s send", connection));
-        executors.add(outgoingExecutor);
-        outgoing = new AsyncDispatch<T>(outgoingExecutor);
-        outgoing.dispatchTo(new FailureHandlingDispatch<T>(connection, dispatchFailureHandler));
-
-        StoppableExecutor dispatchExecutor = executor.create(String.format("%s dispatch", connection));
-        executors.add(dispatchExecutor);
-        stack = new ProtocolStack<T>(dispatchExecutor, dispatchFailureHandler, dispatchFailureHandler, protocols);
-        stack.getBottom().dispatchTo(outgoing);
-
-        StoppableExecutor incomingExecutor = executor.create(String.format("%s receive", connection));
-        executors.add(incomingExecutor);
-        incoming = new AsyncReceive<T>(incomingExecutor);
-        incoming.dispatchTo(stack.getBottom());
-        incoming.receiveFrom(new ConnectionReceive<T>(connection));
-    }
-
-    public void dispatch(T message) {
-        stack.getTop().dispatch(message);
-    }
-
-    public void dispatchTo(Dispatch<? super T> handler) {
-        stack.getTop().dispatchTo(handler);
-    }
-
-    public void stop() {
-        new CompositeStoppable(stack, outgoing, connection, incoming).add(executors).stop();
-    }
-
-    private class ConnectionReceive<T> implements Receive<T> {
-        private final Connection<T> connection;
-
-        public ConnectionReceive(Connection<T> connection) {
-            this.connection = connection;
-        }
-
-        public T receive() {
-            T result = connection.receive();
-            if (result == null) {
-                stack.requestStop();
-            }
-            return result;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectException.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectException.java
deleted file mode 100755
index 9b4dc7d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectException.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.GradleException;
-
-public class ConnectException extends GradleException {
-    public ConnectException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java
deleted file mode 100755
index 018a82c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.Receive;
-
-/**
- * <p>A messaging endpoint which allows push-style dispatch and pull-style receive.
- *
- * <p>Implementations are not guaranteed to be completely thread-safe.
- * However, the implementations:
- * <ul>
- * <li>should allow separate threads for dispatching and receiving, i.e. single thread that dispatches
- * and a different single thread that receives should be perfectly safe</li>
- * <li>should allow stopping or requesting stopping from a different thread than receiving/dispatching</li>
- * <li>don't guarantee allowing multiple threads dispatching</li>
- * </li>
- * </ul>
- */
-public interface Connection<T> extends Dispatch<T>, Receive<T>, AsyncStoppable {
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
deleted file mode 100644
index 679e4a9..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.dispatch.DiscardingFailureHandler;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.dispatch.ReflectionDispatch;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.internal.protocol.ChannelAvailable;
-import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
-import org.gradle.util.IdGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIncomingBroadcast.class);
-    private final ProtocolStack<DiscoveryMessage> protocolStack;
-    private final MessageOriginator messageOriginator;
-    private final String group;
-    private final Lock lock = new ReentrantLock();
-    private final Set<String> channels = new HashSet<String>();
-    private final StoppableExecutor executor;
-    private final Address address;
-    private final MessageHub hub;
-
-    public DefaultIncomingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, IncomingConnector<Message> incomingConnector, ExecutorFactory executorFactory, IdGenerator<?> idGenerator, ClassLoader messagingClassLoader) {
-        this.messageOriginator = messageOriginator;
-        this.group = group;
-
-        executor = executorFactory.create("discovery broadcast");
-        DiscardingFailureHandler<DiscoveryMessage> failureHandler = new DiscardingFailureHandler<DiscoveryMessage>(LOGGER);
-        protocolStack = new ProtocolStack<DiscoveryMessage>(executor, failureHandler, failureHandler, new ChannelRegistrationProtocol(messageOriginator));
-        connection.dispatchTo(new GroupMessageFilter(group, protocolStack.getBottom()));
-        protocolStack.getBottom().dispatchTo(connection);
-
-        address = incomingConnector.accept(new IncomingConnectionAction(), true);
-        hub = new MessageHub("incoming broadcast", messageOriginator.getName(), executorFactory, idGenerator, messagingClassLoader);
-
-        LOGGER.info("Created IncomingBroadcast with {}", messageOriginator);
-    }
-
-    public <T> void addIncoming(Class<T> type, T handler) {
-        String channelKey = type.getName();
-        lock.lock();
-        try {
-            if (channels.add(channelKey)) {
-                protocolStack.getTop().dispatch(new ChannelAvailable(messageOriginator, group, channelKey, address));
-            }
-        } finally {
-            lock.unlock();
-        }
-        hub.addIncoming(channelKey, new TypeCastDispatch<MethodInvocation, Object>(MethodInvocation.class, new ReflectionDispatch(handler)));
-    }
-
-    public void stop() {
-        new CompositeStoppable().add(protocolStack, hub, executor).stop();
-    }
-
-    private class IncomingConnectionAction implements Action<ConnectEvent<Connection<Message>>> {
-        public void execute(ConnectEvent<Connection<Message>> connectionConnectEvent) {
-            hub.addConnection(connectionConnectEvent.getConnection());
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
deleted file mode 100755
index a095887..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.ObjectConnection;
-
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.atomic.AtomicReference;
-
-public class DefaultMessagingServer implements MessagingServer, Stoppable {
-    private final MultiChannelConnector connector;
-    private final ClassLoader classLoader;
-    private final Set<ObjectConnection> connections = new CopyOnWriteArraySet<ObjectConnection>();
-
-    public DefaultMessagingServer(MultiChannelConnector connector, ClassLoader classLoader) {
-        this.connector = connector;
-        this.classLoader = classLoader;
-    }
-
-    public Address accept(final Action<ConnectEvent<ObjectConnection>> action) {
-        return connector.accept(new Action<ConnectEvent<MultiChannelConnection<Object>>>() {
-            public void execute(ConnectEvent<MultiChannelConnection<Object>> connectEvent) {
-                finishConnect(connectEvent, action);
-            }
-        });
-    }
-
-    private void finishConnect(ConnectEvent<MultiChannelConnection<Object>> connectEvent,
-                               Action<ConnectEvent<ObjectConnection>> action) {
-        MultiChannelConnection<Object> messageConnection = connectEvent.getConnection();
-        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(messageConnection);
-        OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(messageConnection);
-        AtomicReference<ObjectConnection> connectionRef = new AtomicReference<ObjectConnection>();
-        AsyncStoppable stopControl = new ConnectionAsyncStoppable(messageConnection, connectionRef);
-
-        DefaultObjectConnection connection = new DefaultObjectConnection(messageConnection, stopControl, outgoing, incoming);
-        connectionRef.set(connection);
-        connections.add(connection);
-        action.execute(new ConnectEvent<ObjectConnection>(connection, connectEvent.getLocalAddress(), connectEvent.getRemoteAddress()));
-    }
-
-    public void stop() {
-        for (ObjectConnection connection : connections) {
-            connection.requestStop();
-        }
-        try {
-            new CompositeStoppable(connections).stop();
-        } finally {
-            connections.clear();
-        }
-    }
-
-    private class ConnectionAsyncStoppable implements AsyncStoppable {
-        private final MultiChannelConnection<Object> messageConnection;
-        private final AtomicReference<ObjectConnection> connectionRef;
-
-        public ConnectionAsyncStoppable(MultiChannelConnection<Object> messageConnection,
-                                        AtomicReference<ObjectConnection> connectionRef) {
-            this.messageConnection = messageConnection;
-            this.connectionRef = connectionRef;
-        }
-
-        public void requestStop() {
-            messageConnection.requestStop();
-        }
-
-        public void stop() {
-            try {
-                messageConnection.stop();
-            } finally {
-                connections.remove(connectionRef.get());
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
deleted file mode 100755
index b1c88f4..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.internal.Stoppable;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.util.IdGenerator;
-import org.gradle.util.UUIDGenerator;
-
-import java.util.UUID;
-
-public class DefaultMultiChannelConnector implements MultiChannelConnector, Stoppable {
-    private final OutgoingConnector<Message> outgoingConnector;
-    private final ExecutorFactory executorFactory;
-    private final StoppableExecutor executorService;
-    private final HandshakeIncomingConnector incomingConnector;
-    private final IdGenerator<UUID> idGenerator = new UUIDGenerator();
-    private final ClassLoader messagingClassLoader;
-
-    public DefaultMultiChannelConnector(OutgoingConnector<Message> outgoingConnector, IncomingConnector<Message> incomingConnector,
-                                        ExecutorFactory executorFactory, ClassLoader messagingClassLoader) {
-        this.messagingClassLoader = messagingClassLoader;
-        this.outgoingConnector = new HandshakeOutgoingConnector(outgoingConnector);
-        this.executorFactory = executorFactory;
-        executorService = executorFactory.create("Incoming Connection Handler");
-        this.incomingConnector = new HandshakeIncomingConnector(incomingConnector, executorService);
-    }
-
-    public void stop() {
-        executorService.stop();
-    }
-
-    public Address accept(final Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
-        Action<ConnectEvent<Connection<Message>>> connectAction = new Action<ConnectEvent<Connection<Message>>>() {
-            public void execute(ConnectEvent<Connection<Message>> event) {
-                finishConnect(event, action);
-            }
-        };
-        return incomingConnector.accept(connectAction, false);
-    }
-
-    private void finishConnect(ConnectEvent<Connection<Message>> event,
-                               Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
-        Address localAddress = event.getLocalAddress();
-        Address remoteAddress = event.getRemoteAddress();
-        MessageHub hub = new MessageHub(String.format("Incoming Connection %s", localAddress), "message server", executorFactory, idGenerator, messagingClassLoader);
-        DefaultMultiChannelConnection channelConnection = new DefaultMultiChannelConnection(hub, event.getConnection(), localAddress, remoteAddress);
-        action.execute(new ConnectEvent<MultiChannelConnection<Object>>(channelConnection, localAddress, remoteAddress));
-    }
-
-    public MultiChannelConnection<Object> connect(Address destinationAddress) {
-        Connection<Message> connection = outgoingConnector.connect(destinationAddress);
-        MessageHub hub = new MessageHub(String.format("Outgoing Connection %s", destinationAddress), "message client", executorFactory, idGenerator, messagingClassLoader);
-        return new DefaultMultiChannelConnection(hub, connection, null, destinationAddress);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
deleted file mode 100755
index e1f621c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.Addressable;
-import org.gradle.messaging.remote.ObjectConnection;
-
-public class DefaultObjectConnection implements ObjectConnection {
-    private final Addressable addressable;
-    private final AsyncStoppable stopControl;
-    private final OutgoingMethodInvocationHandler outgoing;
-    private final IncomingMethodInvocationHandler incoming;
-
-    public DefaultObjectConnection(Addressable addressable, AsyncStoppable stopControl,
-                                   OutgoingMethodInvocationHandler outgoing, IncomingMethodInvocationHandler incoming) {
-        this.addressable = addressable;
-        this.stopControl = stopControl;
-        this.outgoing = outgoing;
-        this.incoming = incoming;
-    }
-
-    public Address getRemoteAddress() {
-        return addressable.getRemoteAddress();
-    }
-
-    public Address getLocalAddress() {
-        return addressable.getLocalAddress();
-    }
-
-    public <T> void addIncoming(Class<T> type, T instance) {
-        incoming.addIncoming(type, instance);
-    }
-
-    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
-        incoming.addIncoming(type, dispatch);
-    }
-
-    public <T> T addOutgoing(Class<T> type) {
-        return outgoing.addOutgoing(type);
-    }
-
-    public void requestStop() {
-        stopControl.requestStop();
-    }
-
-    public void stop() {
-        stopControl.stop();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
deleted file mode 100644
index bc5bb18..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.dispatch.DiscardingFailureHandler;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.DispatchFailureHandler;
-import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.internal.protocol.ChannelAvailable;
-import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
-import org.gradle.messaging.remote.internal.protocol.LookupRequest;
-import org.gradle.util.IdGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class DefaultOutgoingBroadcast implements OutgoingBroadcast, Stoppable {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOutgoingBroadcast.class);
-    private final MessageOriginator messageOriginator;
-    private final String group;
-    private final OutgoingConnector<Message> outgoingConnector;
-    private final ProtocolStack<DiscoveryMessage> discoveryBroadcast;
-    private final Lock lock = new ReentrantLock();
-    private final StoppableExecutor executor;
-    private final Set<String> channels = new HashSet<String>();
-    private final Set<Address> connections = new HashSet<Address>();
-    private final MessageHub hub;
-
-    public DefaultOutgoingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, OutgoingConnector<Message> outgoingConnector, ExecutorFactory executorFactory, final IdGenerator<?> idGenerator, ClassLoader messagingClassLoader) {
-        this.messageOriginator = messageOriginator;
-        this.group = group;
-        this.outgoingConnector = outgoingConnector;
-        DispatchFailureHandler<Object> failureHandler = new DiscardingFailureHandler<Object>(LOGGER);
-
-        hub = new MessageHub("outgoing broadcast", messageOriginator.getName(), executorFactory, idGenerator, messagingClassLoader);
-
-        executor = executorFactory.create("broadcast lookup");
-        discoveryBroadcast = new ProtocolStack<DiscoveryMessage>(executor, failureHandler, failureHandler, new ChannelLookupProtocol());
-        connection.dispatchTo(new GroupMessageFilter(group, discoveryBroadcast.getBottom()));
-        discoveryBroadcast.getBottom().dispatchTo(connection);
-        discoveryBroadcast.getTop().dispatchTo(new DiscoveryMessageDispatch());
-
-        LOGGER.info("Created OutgoingBroadcast with {}", messageOriginator);
-    }
-
-    public <T> T addOutgoing(Class<T> type) {
-        String channelKey = type.getName();
-        lock.lock();
-        try {
-            if (channels.add(channelKey)) {
-                discoveryBroadcast.getTop().dispatch(new LookupRequest(messageOriginator, group, channelKey));
-            }
-        } finally {
-            lock.unlock();
-        }
-        return new ProxyDispatchAdapter<T>(hub.addMulticastOutgoing(channelKey), type).getSource();
-    }
-
-    public void stop() {
-        CompositeStoppable stoppable = new CompositeStoppable();
-        lock.lock();
-        try {
-            stoppable.add(hub, discoveryBroadcast, executor);
-        } finally {
-            connections.clear();
-            lock.unlock();
-        }
-        stoppable.stop();
-    }
-
-    private class DiscoveryMessageDispatch implements Dispatch<DiscoveryMessage> {
-        public void dispatch(DiscoveryMessage message) {
-            if (message instanceof ChannelAvailable) {
-                ChannelAvailable available = (ChannelAvailable) message;
-                Address serviceAddress = available.getAddress();
-                lock.lock();
-                try {
-                    if (!channels.contains(available.getChannel())) {
-                        return;
-                    }
-                    if (connections.contains(serviceAddress)) {
-                        return;
-                    }
-                    connections.add(serviceAddress);
-                } finally {
-                    lock.unlock();
-                }
-
-                Connection<Message> syncConnection = outgoingConnector.connect(serviceAddress);
-                hub.addConnection(syncConnection);
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java
deleted file mode 100644
index 6cecaa3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * A {@link DisconnectAwareConnection} implementation that decorates an existing connection, adding disconnect awareness.
- * <p>
- * This implementation uses {@link EagerReceiveBuffer} internally to receive messages as fast as they are sent.
- * The messages are then collected by {@link #receive()} as per normal.
- * <p>
- * NOTE: due to the use of a bounded buffer, disconnection may not be detected immediately if the internal buffer is full.
- */
-public class DisconnectAwareConnectionDecorator<T> extends DelegatingConnection<T> implements DisconnectAwareConnection<T> {
-
-    private static final Logger LOGGER = Logging.getLogger(DisconnectAwareConnectionDecorator.class);
-    private static final int DEFAULT_BUFFER_SIZE = 200;
-
-    private final Lock actionLock = new ReentrantLock();
-    private final CountDownLatch actionSetLatch = new CountDownLatch(1);
-    private final EagerReceiveBuffer<T> receiveBuffer;
-    private Runnable disconnectAction;
-
-    private volatile boolean stopped;
-
-    public DisconnectAwareConnectionDecorator(Connection<T> connection, StoppableExecutor executor) {
-        this(connection, executor, DEFAULT_BUFFER_SIZE);
-    }
-
-    public DisconnectAwareConnectionDecorator(Connection<T> connection, StoppableExecutor executor, int bufferSize) {
-        super(connection);
-
-        // EagerReceiveBuffer guaranteest that onReceiversExhausted() will be completed before it returns null from receive(),
-        // which means we satisfy the condition of the DisconnectAwareConnection contract that disconnect handlers must complete
-        // before receive() returns null.
-
-        receiveBuffer = new EagerReceiveBuffer<T>(executor, bufferSize, connection, new Runnable() {
-            public void run() {
-                invokeDisconnectAction();
-            }
-        });
-
-        receiveBuffer.start();
-    }
-
-    public Runnable onDisconnect(Runnable disconnectAction) {
-        actionLock.lock();
-        try {
-            Runnable previous = disconnectAction;
-            this.disconnectAction = disconnectAction;
-            actionSetLatch.countDown();
-            return previous;
-        } finally {
-            actionLock.unlock();
-        }
-    }
-
-    public T receive() {
-        return receiveBuffer.receive();
-    }
-
-    private void invokeDisconnectAction() {
-        if (!stopped) {
-            try {
-                actionSetLatch.await();
-            } catch (InterruptedException e) {
-                throw UncheckedException.throwAsUncheckedException(e);
-            }
-
-            actionLock.lock();
-            try {
-                if (disconnectAction != null) {
-                    LOGGER.debug("about to invoke disconnection handler {}", disconnectAction);
-                    try {
-                        disconnectAction.run();
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                        LOGGER.error("disconnection handler threw exception", e);
-                        throw UncheckedException.throwAsUncheckedException(e);
-                    }
-                    LOGGER.info("completed disconnection handler {}", disconnectAction);
-                }
-            } finally {
-                actionLock.unlock();
-            }
-        }
-    }
-
-    public void requestStop() {
-        stopped = true;
-        onDisconnect(null);
-        super.requestStop();
-    }
-
-    public void stop() {
-        stopped = true;
-        onDisconnect(null);
-        super.stop();
-        receiveBuffer.stop();
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
deleted file mode 100644
index bea80d3..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.dispatch.Receive;
-import org.gradle.messaging.dispatch.AsyncReceive;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-
-import java.util.Collection;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Continuously consumes from on or more receivers, serialising to an in memory buffer for synchronous consumption.
- * <p>
- * Messages from the same receive instance are guaranteed to always be returned from {@link #receive()} in sequence. However, no
- * guarantee is made to deliver messages from different sources in chronological order when multiple multiple receive instances
- * are being consumed from.
- * <p>
- * The buffer is bounded, the size of which is specified at construction or defaulting to {@value DEFAULT_BUFFER_SIZE}.
- * If the buffer fills, the receive threads will block until space becomes available. If a stop is initiated while
- * a thread is waiting for free space in the buffer after having received a message, that message will be discarded.
- * <p>
- * If a stop is initiated while a receive thread is waiting to receive (i.e. is blocked in a {@code receive()} call to the source),
- * the stop will block until this returns. Therefore, it is advised to try to externally stop each of the receive instances being
- * used by the buffer before initiating a stop on the buffer.
- */
-public class EagerReceiveBuffer<T> implements Receive<T>, AsyncStoppable {
-
-    private enum State {
-        Init, Started, Stopping, Stopped
-    }
-
-    private static final Logger LOGGER = Logging.getLogger(EagerReceiveBuffer.class);
-    private static final int DEFAULT_BUFFER_SIZE = 200;
-
-    private final Lock lock = new ReentrantLock();
-    private final Condition notFullOrStop  = lock.newCondition();
-    private final Condition notEmptyOrNoReceivers = lock.newCondition();
-
-
-    private final StoppableExecutor executor;
-    private final int bufferSize;
-    private final Collection<Receive<T>> receivers;
-    private final Runnable onReceiversExhausted;
-    private final CountDownLatch onReceiversExhaustedFinishedLatch = new CountDownLatch(1);
-
-    private final AsyncReceive<T> asyncReceive;
-    private final LinkedList<T> queue = new LinkedList<T>();
-
-    private boolean hasActiveReceivers = true;
-    private State state = State.Init;
-
-    private static <T> Collection<Receive<T>> toReceiveCollection(Receive<T> receiver) {
-        Collection<Receive<T>> list = new ArrayList<Receive<T>>(1);
-        list.add(receiver);
-        return list;
-    }
-
-    public EagerReceiveBuffer(StoppableExecutor executor, Receive<T> receiver) {
-        this(executor, DEFAULT_BUFFER_SIZE, toReceiveCollection(receiver), null);
-    }
-
-    public EagerReceiveBuffer(StoppableExecutor executor, Receive<T> receiver, Runnable onReceiversExhausted) {
-        this(executor, DEFAULT_BUFFER_SIZE, toReceiveCollection(receiver), onReceiversExhausted);
-    }
-
-    public EagerReceiveBuffer(StoppableExecutor executor, Collection<Receive<T>> receivers) {
-        this(executor, DEFAULT_BUFFER_SIZE, receivers, null);
-    }
-
-    public EagerReceiveBuffer(StoppableExecutor executor, Collection<Receive<T>> receivers, Runnable onReceiversExhausted) {
-        this(executor, DEFAULT_BUFFER_SIZE, receivers, onReceiversExhausted);
-    }
-
-    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Receive<T> receiver) {
-        this(executor, bufferSize, toReceiveCollection(receiver), null);
-    }
-
-    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Receive<T> receiver, Runnable onReceiversExhausted) {
-        this(executor, bufferSize, toReceiveCollection(receiver), onReceiversExhausted);
-    }
-
-    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Collection<Receive<T>> receivers) {
-        this(executor, bufferSize, receivers, null);
-    }
-
-    public EagerReceiveBuffer(StoppableExecutor executor, final int bufferSize, Collection<Receive<T>> receivers, final Runnable onReceiversExhausted) {
-        if (receivers.size() == 0) {
-            throw new IllegalArgumentException("eager receive buffer created with no receivers");
-        }
-
-        if (bufferSize < 1) {
-            throw new IllegalArgumentException("eager receive buffer size must be positive (value given: " + bufferSize + ")");
-        }
-
-        this.executor = executor;
-        this.bufferSize = bufferSize;
-        this.receivers = receivers;
-        this.onReceiversExhausted = onReceiversExhausted;
-
-        Dispatch<T> dispatch = new Dispatch<T>() {
-            public void dispatch(T message) {
-                lock.lock();
-                try {
-                    while (queue.size() == bufferSize && state == State.Started) {
-                        try {
-                            notFullOrStop.await();
-                        } catch (InterruptedException e) {
-                            throw UncheckedException.throwAsUncheckedException(e);
-                        }
-                    }
-
-                    queue.add(message);
-                    notEmptyOrNoReceivers.signalAll();
-                } finally {
-                    lock.unlock();
-                }
-            }
-        };
-
-        this.asyncReceive = new AsyncReceive(executor, dispatch, new Runnable() {
-            public void run() {
-                lock.lock();
-                try {
-                    hasActiveReceivers = false;
-                    if (onReceiversExhausted != null) {
-                        onReceiversExhausted.run();
-                    }
-                    notEmptyOrNoReceivers.signalAll();
-                } catch (Throwable t) {
-                    t.printStackTrace();
-                } finally {
-                    lock.unlock();
-                    onReceiversExhaustedFinishedLatch.countDown();
-                }
-            }
-        });
-    }
-
-    /**
-     * Start consuming from the receivers given at construction.
-     *
-     * @throws IllegalStateException if already started
-     */
-    public void start() {
-        lock.lock();
-        try {
-            if (state != State.Init) {
-                throw new IllegalStateException("this eager receive buffer has already been started");
-            }
-            state = State.Started;
-
-            for (Receive<T> receiver : receivers) {
-                asyncReceive.receiveFrom(receiver);
-            }
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Receive the next message from the buffer.
-     *
-     * @return The next message or {@code null} if there are no more messages and no unexhausted receivers.
-     */
-    public T receive() {
-        lock.lock();
-        try {
-            while (queue.isEmpty() && hasActiveReceivers) {
-                try {
-                    notEmptyOrNoReceivers.await();
-                } catch (InterruptedException e) {
-                    throw UncheckedException.throwAsUncheckedException(e);
-                }
-            }
-
-            if (queue.isEmpty()) {
-                // no more messages, and all receivers are exhausted
-                assert !hasActiveReceivers;
-                return null;
-            } else {
-                T message = queue.poll();
-                assert message != null;
-                notFullOrStop.signalAll();
-                return message;
-            }
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    /**
-     * Stops receiving new messages.
-     */
-    public void requestStop() {
-        lock.lock();
-        try {
-            doRequestStop();
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    private void doRequestStop() {
-        asyncReceive.requestStop();
-        if (hasActiveReceivers) {
-            setState(State.Stopping);
-        } else {
-            setState(State.Stopped);
-        }
-    }
-
-    private void setState(State state) {
-        this.state = state;
-        notFullOrStop.signalAll(); // wake up any consumers waiting for space (assume it's a stopish state)
-    }
-
-    /**
-     * Stops receiving new messages. Blocks until all queued messages have been delivered.
-     */
-    public void stop() {
-        lock.lock();
-        try {
-            doRequestStop();
-        } finally {
-            lock.unlock();
-        }
-
-        // Have to relinquish lock at this point because the onReceiversExhausted callback that we pass to the async
-        // runnable needs to acquire the lock in order to signal the notEmptyOrNoReceivers condition. If we didn't
-        // relinquish we would have deadlock. This is harmless due to this method being idempotent.
-        try {
-            onReceiversExhaustedFinishedLatch.await();
-        } catch (InterruptedException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-
-        lock.lock();
-        try {
-            asyncReceive.stop();
-            setState(State.Stopped);
-        } finally {
-            lock.unlock();
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/InputForwarder.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/InputForwarder.java
deleted file mode 100644
index 7b02f68..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/InputForwarder.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.Action;
-import org.gradle.internal.Stoppable;
-import org.gradle.internal.UncheckedException;
-import org.gradle.util.DisconnectableInputStream;
-import org.gradle.util.LineBufferingOutputStream;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.channels.AsynchronousCloseException;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Asynchronously consumes from an input stream for a time,
- * forwarding a <strong>line</strong> of input at a time to a specified action.
- *
- * Note that calling stop() will NOT close the source input stream.
- */
-public class InputForwarder implements Stoppable {
-
-    private final InputStream input;
-    private final Action<String> forwardTo;
-    private final Runnable onFinish;
-    private final ExecutorFactory executorFactory;
-    private final int bufferSize;
-    private StoppableExecutor forwardingExecuter;
-    private DisconnectableInputStream disconnectableInput;
-    private LineBufferingOutputStream outputBuffer;
-    private final Lock lifecycleLock = new ReentrantLock();
-    private boolean started;
-    private boolean stopped;
-
-    public InputForwarder(InputStream input, Action<String> forwardTo, Runnable onFinish, ExecutorFactory executerFactory, int bufferSize) {
-        this.input = input;
-        this.forwardTo = forwardTo;
-        this.onFinish = onFinish;
-        this.executorFactory = executerFactory;
-        this.bufferSize = bufferSize;
-    }
-
-    public InputForwarder start() {
-        lifecycleLock.lock();
-        try {
-            if (started) {
-                throw new IllegalStateException("input forwarder has already been started");
-            }
-
-            disconnectableInput = new DisconnectableInputStream(input, executorFactory, bufferSize);
-            outputBuffer = new LineBufferingOutputStream(forwardTo, true, bufferSize);
-
-            forwardingExecuter = executorFactory.create("forward input");
-            forwardingExecuter.execute(new Runnable() {
-                public void run() {
-                    byte[] buffer = new byte[bufferSize];
-                    int readCount;
-                    try {
-                        while (true) {
-                            try {
-                                readCount = disconnectableInput.read(buffer, 0, bufferSize);
-                                if (readCount < 0) {
-                                    break;
-                                }
-                            } catch (AsynchronousCloseException e) {
-                                break;
-                            } catch (IOException e) {
-                                // Unsure what the best thing to do is here, should we forward the error?
-                                throw UncheckedException.throwAsUncheckedException(e);
-                            }
-
-                            try {
-                                outputBuffer.write(buffer, 0, readCount);
-                            } catch (IOException e) {
-                                // this shouldn't happen as outputBuffer will only throw if close has been called
-                                // and we own this object exclusively and will not have done that at this time
-                                throw UncheckedException.throwAsUncheckedException(e);
-                            }
-                        }
-                    } finally {
-                        try {
-                            outputBuffer.close(); // will flush any unterminated lines out synchronously
-                        } catch (IOException e) {
-                            throw UncheckedException.throwAsUncheckedException(e);
-                        }
-                    }
-                    
-                    onFinish.run();
-                }
-            });
-
-            started = true;
-        } finally {
-            lifecycleLock.unlock();
-        }
-        
-        return this;
-    }
-
-    public void stop() {
-        lifecycleLock.lock();
-        try {
-            if (!stopped) {
-                try {
-                    disconnectableInput.close();
-                } catch (IOException e) {
-                    throw UncheckedException.throwAsUncheckedException(e);
-                }
-                
-                forwardingExecuter.stop();
-                stopped = true;
-            }
-        } finally {
-            lifecycleLock.unlock();
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
deleted file mode 100755
index 190ae00..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.UncheckedException;
-import org.gradle.util.ClassLoaderObjectInputStream;
-
-import java.io.*;
-import java.lang.reflect.Constructor;
-
-public abstract class Message implements Serializable {
-    public static void send(Object message, OutputStream outputSteam) throws IOException {
-        ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outputSteam);
-        try {
-            oos.writeObject(message);
-        } finally {
-            oos.flush();
-        }
-    }
-
-    public static Object receive(InputStream inputSteam, ClassLoader classLoader)
-            throws IOException, ClassNotFoundException {
-        ObjectInputStream ois = new ExceptionReplacingObjectInputStream(inputSteam, classLoader);
-        return ois.readObject();
-    }
-
-    private static class ExceptionPlaceholder implements Serializable {
-        private byte[] serializedException;
-        private String type;
-        private String message;
-        private ExceptionPlaceholder cause;
-        private StackTraceElement[] stackTrace;
-
-        public ExceptionPlaceholder(final Throwable throwable) throws IOException {
-            ByteArrayOutputStream outstr = new ByteArrayOutputStream();
-            ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outstr) {
-                @Override
-                protected Object replaceObject(Object obj) throws IOException {
-                    if (obj == throwable) {
-                        return throwable;
-                    }
-                    // Don't serialize the cause - we'll serialize it separately later 
-                    if (obj == throwable.getCause()) {
-                        return new CausePlaceholder();
-                    }
-                    return super.replaceObject(obj);
-                }
-            };
-            try {
-                oos.writeObject(throwable);
-                oos.close();
-                serializedException = outstr.toByteArray();
-            } catch (NotSerializableException e) {
-                // Ignore
-            }
-
-            type = throwable.getClass().getName();
-            message = throwable.getMessage();
-            if (throwable.getCause() != null) {
-                cause = new ExceptionPlaceholder(throwable.getCause());
-            }
-            stackTrace = throwable.getStackTrace();
-        }
-
-        public Throwable read(ClassLoader classLoader) throws IOException {
-            final Throwable causeThrowable = getCause(classLoader);
-            Throwable throwable = null;
-            if (serializedException != null) {
-                try {
-                    final ExceptionReplacingObjectInputStream ois = new ExceptionReplacingObjectInputStream(new ByteArrayInputStream(serializedException), classLoader) {
-                        @Override
-                        protected Object resolveObject(Object obj) throws IOException {
-                            if (obj instanceof CausePlaceholder) {
-                                return causeThrowable;
-                            }
-                            return super.resolveObject(obj);
-                        }
-                    };
-                    throwable = (Throwable) ois.readObject();
-                } catch (ClassNotFoundException e) {
-                    // Ignore
-                } catch (InvalidClassException e) {
-                    try {
-                        Constructor<?> constructor = classLoader.loadClass(type).getConstructor(String.class);
-                        throwable = (Throwable) constructor.newInstance(message);
-                        throwable.initCause(causeThrowable);
-                        throwable.setStackTrace(stackTrace);
-                    } catch (ClassNotFoundException e1) {
-                        // Ignore
-                    } catch (NoSuchMethodException e1) {
-                        // Ignore
-                    } catch (Throwable t) {
-                        throw UncheckedException.throwAsUncheckedException(t);
-                    }
-                }
-            }
-
-            if (throwable == null) {
-                throwable = new PlaceholderException(type, message, causeThrowable);
-                throwable.setStackTrace(stackTrace);
-            }
-
-            return throwable;
-        }
-
-        private Throwable getCause(ClassLoader classLoader) throws IOException {
-            return cause != null ? cause.read(classLoader) : null;
-        }
-    }
-
-    private static class CausePlaceholder implements Serializable {
-    }
-
-    private static class TopLevelExceptionPlaceholder extends ExceptionPlaceholder {
-        private TopLevelExceptionPlaceholder(Throwable throwable) throws IOException {
-            super(throwable);
-        }
-    }
-
-    private static class ExceptionReplacingObjectOutputStream extends ObjectOutputStream {
-        public ExceptionReplacingObjectOutputStream(OutputStream outputSteam) throws IOException {
-            super(outputSteam);
-            enableReplaceObject(true);
-        }
-
-        @Override
-        protected Object replaceObject(Object obj) throws IOException {
-            if (obj instanceof Throwable) {
-                return new TopLevelExceptionPlaceholder((Throwable) obj);
-            }
-            return obj;
-        }
-    }
-
-    private static class ExceptionReplacingObjectInputStream extends ClassLoaderObjectInputStream {
-        public ExceptionReplacingObjectInputStream(InputStream inputSteam, ClassLoader classLoader) throws IOException {
-            super(inputSteam, classLoader);
-            enableResolveObject(true);
-        }
-
-        @Override
-        protected Object resolveObject(Object obj) throws IOException {
-            if (obj instanceof TopLevelExceptionPlaceholder) {
-                return ((ExceptionPlaceholder) obj).read(getClassLoader());
-            }
-            return obj;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageHub.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageHub.java
deleted file mode 100644
index 07f87eb..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageHub.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.dispatch.DiscardingFailureHandler;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.DispatchFailureHandler;
-import org.gradle.messaging.remote.internal.protocol.EndOfStreamEvent;
-import org.gradle.util.IdGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class MessageHub implements AsyncStoppable {
-    private final Lock lock = new ReentrantLock();
-    private final CompositeStoppable executors = new CompositeStoppable();
-    private final CompositeStoppable connections = new CompositeStoppable();
-    private final Collection<ProtocolStack<Message>> handlers = new ArrayList<ProtocolStack<Message>>();
-    private final Collection<ProtocolStack<Message>> workers = new ArrayList<ProtocolStack<Message>>();
-    private final Map<String, ProtocolStack<Message>> outgoingUnicasts = new HashMap<String, ProtocolStack<Message>>();
-    private final Map<String, ProtocolStack<Message>> outgoingBroadcasts = new HashMap<String, ProtocolStack<Message>>();
-    private final DispatchFailureHandler<Object> failureHandler;
-    private final Router router;
-    private final String displayName;
-    private final String nodeName;
-    private final ExecutorFactory executorFactory;
-    private final IdGenerator<?> idGenerator;
-    private final ClassLoader messagingClassLoader;
-    private final StoppableExecutor incomingExecutor;
-
-    public MessageHub(String displayName, String nodeName, ExecutorFactory executorFactory, IdGenerator<?> idGenerator, ClassLoader messagingClassLoader) {
-        this.displayName = displayName;
-        this.nodeName = nodeName;
-        this.executorFactory = executorFactory;
-        this.idGenerator = idGenerator;
-        this.messagingClassLoader = messagingClassLoader;
-        failureHandler = new DiscardingFailureHandler<Object>(LoggerFactory.getLogger(MessageHub.class));
-        StoppableExecutor executor = executorFactory.create(displayName + " message router");
-        executors.add(executor);
-        router = new Router(executor, failureHandler);
-
-        incomingExecutor = executorFactory.create(displayName + " worker");
-        executors.add(incomingExecutor);
-    }
-
-    /**
-     * Adds an incoming connection. Stops the connection when finished with it.
-     */
-    public void addConnection(Connection<Message> connection) {
-        lock.lock();
-        try {
-            Connection<Message> wrapper = new EndOfStreamConnection(connection);
-            AsyncConnectionAdapter<Message> asyncConnection = new AsyncConnectionAdapter<Message>(wrapper, failureHandler, executorFactory, new RemoteDisconnectProtocol());
-            connections.add(asyncConnection);
-
-            AsyncConnection<Message> incomingEndpoint = router.createRemoteConnection();
-            incomingEndpoint.dispatchTo(new MethodInvocationMarshallingDispatch(asyncConnection));
-            asyncConnection.dispatchTo(new MethodInvocationUnmarshallingDispatch(incomingEndpoint, messagingClassLoader));
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    public Dispatch<Object> addUnicastOutgoing(String channel) {
-        lock.lock();
-        try {
-            ProtocolStack<Message> outgoing = outgoingUnicasts.get(channel);
-            if (outgoing == null) {
-                Protocol<Message> unicastSendProtocol = new UnicastSendProtocol();
-                Protocol<Message> sendProtocol = new SendProtocol(idGenerator.generateId(), nodeName, channel);
-                StoppableExecutor executor = executorFactory.create(displayName + " outgoing " + channel);
-                executors.add(executor);
-                outgoing = new ProtocolStack<Message>(executor, failureHandler, failureHandler, unicastSendProtocol, sendProtocol);
-                outgoingUnicasts.put(channel, outgoing);
-
-                AsyncConnection<Message> outgoingEndpoint = router.createLocalConnection();
-                outgoing.getBottom().dispatchTo(outgoingEndpoint);
-                outgoingEndpoint.dispatchTo(outgoing.getBottom());
-            }
-            return new OutgoingMultiplex(channel, outgoing.getTop());
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    public Dispatch<Object> addMulticastOutgoing(String channel) {
-        lock.lock();
-        try {
-            ProtocolStack<Message> outgoing = outgoingBroadcasts.get(channel);
-            if (outgoing == null) {
-                Protocol<Message> broadcastProtocol = new BroadcastSendProtocol();
-                Protocol<Message> sendProtocol = new SendProtocol(idGenerator.generateId(), nodeName, channel);
-                StoppableExecutor executor = executorFactory.create(displayName + " outgoing broadcast " + channel);
-                executors.add(executor);
-                outgoing = new ProtocolStack<Message>(executor, failureHandler, failureHandler, broadcastProtocol, sendProtocol);
-                outgoingBroadcasts.put(channel, outgoing);
-
-                AsyncConnection<Message> outgoingEndpoint = router.createLocalConnection();
-                outgoing.getBottom().dispatchTo(outgoingEndpoint);
-                outgoingEndpoint.dispatchTo(outgoing.getBottom());
-            }
-            return new OutgoingMultiplex(channel, outgoing.getTop());
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    public void addIncoming(final String channel, final Dispatch<Object> dispatch) {
-        lock.lock();
-        try {
-            final Object id = idGenerator.generateId();
-            Protocol<Message> workerProtocol = new WorkerProtocol(dispatch);
-            Protocol<Message> receiveProtocol = new ReceiveProtocol(id, nodeName, channel);
-
-            ProtocolStack<Message> workerStack = new ProtocolStack<Message>(incomingExecutor, failureHandler, failureHandler, workerProtocol);
-            workers.add(workerStack);
-            ProtocolStack<Message> stack = new ProtocolStack<Message>(incomingExecutor, failureHandler, failureHandler, new BufferingProtocol(200), receiveProtocol);
-            handlers.add(stack);
-
-            workerStack.getBottom().dispatchTo(stack.getTop());
-            stack.getTop().dispatchTo(workerStack.getBottom());
-
-            AsyncConnection<Message> incomingEndpoint = router.createLocalConnection();
-            stack.getBottom().dispatchTo(incomingEndpoint);
-            incomingEndpoint.dispatchTo(stack.getBottom());
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    public void requestStop() {
-        lock.lock();
-        try {
-            for (ProtocolStack<Message> stack : outgoingUnicasts.values()) {
-                stack.requestStop();
-            }
-            for (ProtocolStack<Message> stack : outgoingBroadcasts.values()) {
-                stack.requestStop();
-            }
-            for (ProtocolStack<?> worker : workers) {
-                worker.requestStop();
-            }
-        } finally {
-            lock.unlock();
-        }
-    }
-
-    public void stop() {
-        requestStop();
-
-        CompositeStoppable stoppable = new CompositeStoppable();
-        lock.lock();
-        try {
-            stoppable.add(outgoingUnicasts.values());
-            stoppable.add(outgoingBroadcasts.values());
-            stoppable.add(workers);
-            stoppable.add(handlers);
-            stoppable.add(connections);
-            stoppable.add(router);
-            stoppable.add(executors);
-        } finally {
-            outgoingUnicasts.clear();
-            outgoingBroadcasts.clear();
-            workers.clear();
-            handlers.clear();
-            lock.unlock();
-        }
-
-        stoppable.stop();
-    }
-
-    private static class EndOfStreamConnection extends DelegatingConnection<Message> {
-        private static final Logger LOGGER = LoggerFactory.getLogger(EndOfStreamConnection.class);
-        boolean incomingFinished;
-
-        private EndOfStreamConnection(Connection<Message> connection) {
-            super(connection);
-        }
-
-        @Override
-        public Message receive() {
-            if (incomingFinished) {
-                return null;
-            }
-            Message result;
-            try {
-                result = super.receive();
-            } catch (Throwable e) {
-                LOGGER.error("Could not receive message from connection. Discarding connection.", e);
-                result = null;
-            }
-            if (result instanceof EndOfStreamEvent) {
-                incomingFinished = true;
-            } else if (result == null) {
-                incomingFinished = true;
-                result = new EndOfStreamEvent();
-            }
-            return result;
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageIOException.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageIOException.java
deleted file mode 100644
index b952013..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageIOException.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.GradleException;
-
-public class MessageIOException extends GradleException {
-    public MessageIOException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessagingServices.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessagingServices.java
deleted file mode 100644
index ba5050a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessagingServices.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.service.DefaultServiceRegistry;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.internal.Stoppable;
-import org.gradle.messaging.dispatch.DiscardingFailureHandler;
-import org.gradle.messaging.remote.MessagingClient;
-import org.gradle.messaging.remote.MessagingServer;
-import org.gradle.messaging.remote.internal.inet.*;
-import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
-import org.gradle.messaging.remote.internal.protocol.DiscoveryProtocolSerializer;
-import org.gradle.util.IdGenerator;
-import org.gradle.util.UUIDGenerator;
-import org.gradle.internal.UncheckedException;
-import org.slf4j.LoggerFactory;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.UUID;
-
-/**
- * A factory for a set of messaging services. Provides the following services:
- *
- * <ul>
- *
- * <li>{@link MessagingClient}</li>
- *
- * <li>{@link MessagingServer}</li>
- *
- * <li>{@link OutgoingBroadcast}</li>
- *
- * <li>{@link IncomingBroadcast}</li>
- *
- * </ul>
- */
-public class MessagingServices extends DefaultServiceRegistry implements Stoppable {
-    private final IdGenerator<UUID> idGenerator = new UUIDGenerator();
-    private final ClassLoader messageClassLoader;
-    private final String broadcastGroup;
-    private final SocketInetAddress broadcastAddress;
-    private DefaultMessagingClient messagingClient;
-    private DefaultMultiChannelConnector multiChannelConnector;
-    private TcpIncomingConnector<Message> incomingConnector;
-    private DefaultExecutorFactory executorFactory;
-    private DefaultMessagingServer messagingServer;
-    private DefaultIncomingBroadcast incomingBroadcast;
-    private AsyncConnectionAdapter<DiscoveryMessage> multicastConnection;
-    private DefaultOutgoingBroadcast outgoingBroadcast;
-
-    public MessagingServices(ClassLoader messageClassLoader) {
-        this(messageClassLoader, "gradle");
-    }
-
-    public MessagingServices(ClassLoader messageClassLoader, String broadcastGroup) {
-        this(messageClassLoader, broadcastGroup, defaultBroadcastAddress());
-    }
-
-    public MessagingServices(ClassLoader messageClassLoader, String broadcastGroup, SocketInetAddress broadcastAddress) {
-        this.messageClassLoader = messageClassLoader;
-        this.broadcastGroup = broadcastGroup;
-        this.broadcastAddress = broadcastAddress;
-    }
-
-    private static SocketInetAddress defaultBroadcastAddress() {
-        try {
-            return new SocketInetAddress(InetAddress.getByName("233.253.17.122"), 7912);
-        } catch (UnknownHostException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-
-    private static String determineNodeName() {
-        String hostName;
-        try {
-            hostName = InetAddress.getLocalHost().getHostName();
-        } catch (UnknownHostException e) {
-            hostName = new InetAddressFactory().findRemoteAddresses().get(0).toString();
-        }
-        return String.format("%s@%s", System.getProperty("user.name"), hostName);
-    }
-
-    public void stop() {
-        close();
-    }
-
-    @Override
-    public void close() {
-        CompositeStoppable stoppable = new CompositeStoppable();
-        stoppable.add(incomingConnector);
-        stoppable.add(messagingClient);
-        stoppable.add(messagingServer);
-        stoppable.add(multiChannelConnector);
-        stoppable.add(outgoingBroadcast);
-        stoppable.add(incomingBroadcast);
-        stoppable.add(multicastConnection);
-        stoppable.add(executorFactory);
-        stoppable.stop();
-    }
-
-    protected MessageOriginator createMessageOriginator() {
-        return new MessageOriginator(idGenerator.generateId(), determineNodeName());
-    }
-
-    protected ExecutorFactory createExecutorFactory() {
-        executorFactory = new DefaultExecutorFactory();
-        return executorFactory;
-    }
-
-    protected OutgoingConnector<Message> createOutgoingConnector() {
-        return new TcpOutgoingConnector<Message>(
-                new DefaultMessageSerializer<Message>(
-                        messageClassLoader));
-    }
-
-    protected IncomingConnector<Message> createIncomingConnector() {
-        incomingConnector = new TcpIncomingConnector<Message>(
-                get(ExecutorFactory.class),
-                new DefaultMessageSerializer<Message>(
-                        messageClassLoader),
-                new InetAddressFactory(),
-                idGenerator);
-        return incomingConnector;
-    }
-
-    protected MultiChannelConnector createMultiChannelConnector() {
-        multiChannelConnector = new DefaultMultiChannelConnector(
-                get(OutgoingConnector.class),
-                get(IncomingConnector.class),
-                get(ExecutorFactory.class),
-                messageClassLoader);
-        return multiChannelConnector;
-    }
-
-    protected MessagingClient createMessagingClient() {
-        messagingClient = new DefaultMessagingClient(
-                get(MultiChannelConnector.class),
-                messageClassLoader);
-        return messagingClient;
-    }
-
-    protected MessagingServer createMessagingServer() {
-        messagingServer = new DefaultMessagingServer(
-                get(MultiChannelConnector.class),
-                messageClassLoader);
-        return messagingServer;
-    }
-
-    protected IncomingBroadcast createIncomingBroadcast() {
-        incomingBroadcast = new DefaultIncomingBroadcast(
-                get(MessageOriginator.class),
-                broadcastGroup,
-                get(AsyncConnection.class),
-                get(IncomingConnector.class),
-                get(ExecutorFactory.class),
-                idGenerator,
-                messageClassLoader);
-        return incomingBroadcast;
-    }
-
-    protected OutgoingBroadcast createOutgoingBroadcast() {
-        outgoingBroadcast = new DefaultOutgoingBroadcast(
-                get(MessageOriginator.class),
-                broadcastGroup,
-                get(AsyncConnection.class),
-                get(OutgoingConnector.class),
-                get(ExecutorFactory.class),
-                idGenerator,
-                messageClassLoader);
-        return outgoingBroadcast;
-    }
-
-    protected AsyncConnection<DiscoveryMessage> createMulticastConnection() {
-        MulticastConnection<DiscoveryMessage> connection = new MulticastConnection<DiscoveryMessage>(broadcastAddress, new DiscoveryProtocolSerializer());
-        multicastConnection = new AsyncConnectionAdapter<DiscoveryMessage>(
-                connection,
-                new DiscardingFailureHandler<DiscoveryMessage>(LoggerFactory.getLogger(MulticastConnection.class)),
-                get(ExecutorFactory.class));
-        return multicastConnection;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java
deleted file mode 100755
index 44375d9..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.remote.Addressable;
-import org.gradle.messaging.dispatch.Dispatch;
-
-public interface MultiChannelConnection<T> extends Addressable, AsyncStoppable {
-    /**
-     * Adds a destination for outgoing messages on the given channel. The returned value is thread-safe.
-     */
-    Dispatch<T> addOutgoingChannel(String channelKey);
-
-    /**
-     * Adds a handler for incoming messages on the given channel. The given dispatch is only ever used by a single
-     * thread at any given time.
-     */
-    void addIncomingChannel(String channelKey, Dispatch<T> dispatch);
-
-    /**
-     * Commences graceful stop of this connection. Stops accepting any more outgoing messages, and requests that the
-     * peer stop sending incoming messages.
-     */
-    void requestStop();
-
-    /**
-     * Performs a graceful stop of this connection. Blocks until all dispatched incoming messages have been handled, and
-     * all outgoing messages have been delivered.
-     */
-    void stop();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java
deleted file mode 100755
index 7d64769..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-/**
- * A {@code PlaceholderException} is used when an exception cannot be serialized or deserialized.
- */
-public class PlaceholderException extends RuntimeException {
-    private final String exceptionClassName;
-    
-    public PlaceholderException(String exceptionClassName, String message, Throwable cause) {
-        super(message, cause);
-        this.exceptionClassName = exceptionClassName;
-    }
-    
-    public String toString() {
-        return String.format("%s: %s", exceptionClassName, getMessage());
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolStack.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolStack.java
deleted file mode 100644
index 137b699..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolStack.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.*;
-import org.gradle.util.TrueTimeProvider;
-
-import java.util.LinkedList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public class ProtocolStack<T> implements AsyncStoppable {
-    private final AsyncDispatch<Runnable> workQueue;
-    private final QueuingDispatch<T> incomingQueue = new QueuingDispatch<T>();
-    private final QueuingDispatch<T> outgoingQueue = new QueuingDispatch<T>();
-    private final AsyncReceive<Runnable> receiver;
-    private final DelayedReceive<Runnable> callbackQueue;
-    private final LinkedList<Stage> stack = new LinkedList<Stage>();
-    private final LinkedList<Runnable> contextQueue = new LinkedList<Runnable>();
-    private final DispatchFailureHandler<? super T> outgoingDispatchFailureHandler;
-    private final DispatchFailureHandler<? super T> incomingDispatchFailureHandler;
-    private final CountDownLatch protocolsStopped;
-    private final AtomicBoolean stopRequested = new AtomicBoolean();
-    private final AsyncConnection<T> bottomConnection;
-    private final AsyncConnection<T> topConnection;
-
-    public ProtocolStack(Executor executor, DispatchFailureHandler<? super T> outgoingDispatchFailureHandler, DispatchFailureHandler<? super T> incomingDispatchFailureHandler,
-                         Protocol<T>... protocols) {
-        this.outgoingDispatchFailureHandler = outgoingDispatchFailureHandler;
-        this.incomingDispatchFailureHandler = incomingDispatchFailureHandler;
-        this.callbackQueue = new DelayedReceive<Runnable>(new TrueTimeProvider());
-        protocolsStopped = new CountDownLatch(protocols.length);
-
-        //Start work queue
-        workQueue = new AsyncDispatch<Runnable>(executor);
-        workQueue.dispatchTo(new ExecuteRunnable());
-
-        stack.add(new TopStage());
-        for (Protocol<T> protocol : protocols) {
-            stack.add(new ProtocolStage(protocol));
-        }
-        stack.add(new BottomStage());
-        for (int i = 0; i < stack.size(); i++) {
-            Stage context = stack.get(i);
-            Stage outgoingStage = i == stack.size() - 1 ? null : stack.get(i + 1);
-            Stage incomingStage = i == 0 ? null : stack.get(i - 1);
-            context.attach(outgoingStage, incomingStage);
-        }
-
-        // Wire up callback queue
-        receiver = new AsyncReceive<Runnable>(executor);
-        receiver.dispatchTo(workQueue);
-        receiver.receiveFrom(callbackQueue);
-
-        bottomConnection = new BottomConnection();
-        topConnection = new TopConnection();
-
-        // Start each protocol from bottom to top
-        workQueue.dispatch(new Runnable() {
-            public void run() {
-                for (int i = stack.size() - 1; i >= 0; i--) {
-                    Stage context = stack.get(i);
-                    context.start();
-                }
-            }
-        });
-    }
-
-    public AsyncConnection<T> getBottom() {
-        return bottomConnection;
-    }
-
-    public AsyncConnection<T> getTop() {
-        return topConnection;
-    }
-
-    public void requestStop() {
-        if (!stopRequested.getAndSet(true)) {
-            workQueue.dispatch(new Runnable() {
-                public void run() {
-                    stack.getFirst().requestStop();
-                }
-            });
-        }
-    }
-
-    public void stop() {
-        requestStop();
-        try {
-            protocolsStopped.await();
-        } catch (InterruptedException e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-        callbackQueue.clear();
-        new CompositeStoppable(callbackQueue, receiver, workQueue, incomingQueue, outgoingQueue).stop();
-    }
-
-    private class ExecuteRunnable implements Dispatch<Runnable> {
-        public void dispatch(Runnable message) {
-            contextQueue.add(message);
-            while (!contextQueue.isEmpty()) {
-                contextQueue.removeFirst().run();
-            }
-        }
-    }
-
-    private abstract class Stage {
-        protected Stage outgoing;
-        protected Stage incoming;
-
-        public void attach(Stage outgoing, Stage incoming) {
-            this.outgoing = outgoing;
-            this.incoming = incoming;
-        }
-
-        public void start() {
-        }
-
-        public void handleIncoming(T message) {
-            throw new UnsupportedOperationException();
-        }
-
-        public void handleOutgoing(T message) {
-            throw new UnsupportedOperationException();
-        }
-
-        public void requestStop() {
-        }
-    }
-
-    private enum StageState { Init, StopRequested, StopPending, Stopped }
-
-    private class ProtocolStage extends Stage implements ProtocolContext<T> {
-        private final Protocol<T> protocol;
-        private StageState state = StageState.Init;
-
-        private ProtocolStage(Protocol<T> protocol) {
-            this.protocol = protocol;
-        }
-
-        @Override
-        public void start() {
-            protocol.start(this);
-        }
-
-        @Override
-        public void handleIncoming(T message) {
-            try {
-                protocol.handleIncoming(message);
-            } catch (Throwable throwable) {
-                incomingDispatchFailureHandler.dispatchFailed(message, throwable);
-            }
-        }
-
-        @Override
-        public void handleOutgoing(T message) {
-            try {
-                protocol.handleOutgoing(message);
-            } catch (Throwable throwable) {
-                outgoingDispatchFailureHandler.dispatchFailed(message, throwable);
-            }
-        }
-
-        public void dispatchIncoming(final T message) {
-            contextQueue.add(new Runnable() {
-                public void run() {
-                    incoming.handleIncoming(message);
-                }
-            });
-        }
-
-        public void dispatchOutgoing(final T message) {
-            contextQueue.add(new Runnable() {
-                public void run() {
-                    outgoing.handleOutgoing(message);
-                }
-            });
-        }
-
-        public Callback callbackLater(int delay, TimeUnit delayUnits, Runnable action) {
-            DefaultCallback callback = new DefaultCallback(action);
-            callbackQueue.dispatchLater(callback, delay, delayUnits);
-            return callback;
-        }
-
-        public void stopped() {
-            if (state == StageState.Init) {
-                throw new IllegalStateException(String.format("Cannot stop when in %s state.", state));
-            }
-            if (state != StageState.Stopped) {
-                state = StageState.Stopped;
-                protocolsStopped.countDown();
-                contextQueue.add(new Runnable() {
-                    public void run() {
-                        outgoing.requestStop();
-                    }
-                });
-            }
-        }
-
-        public void stopLater() {
-            if (state == StageState.Init || state == StageState.Stopped) {
-                throw new IllegalStateException(String.format("Cannot stop later when in %s state.", state));
-            }
-            state = StageState.StopPending;
-        }
-
-        @Override
-        public void requestStop() {
-            assert state == StageState.Init;
-            state = StageState.StopRequested;
-            protocol.stopRequested();
-            if (state == StageState.StopRequested) {
-                stopped();
-            }
-        }
-
-        private class DefaultCallback implements Runnable, ProtocolContext.Callback {
-            final Runnable action;
-            boolean cancelled;
-
-            private DefaultCallback(Runnable action) {
-                this.action = action;
-            }
-
-            public void cancel() {
-                cancelled = true;
-                callbackQueue.remove(this);
-            }
-
-            public void run() {
-                if (!cancelled && state != StageState.Stopped) {
-                    action.run();
-                }
-            }
-        }
-    }
-
-    private class TopStage extends Stage {
-        @Override
-        public void handleIncoming(T message) {
-            incomingQueue.dispatch(message);
-        }
-
-        @Override
-        public void handleOutgoing(T message) {
-            outgoing.handleOutgoing(message);
-        }
-
-        @Override
-        public void requestStop() {
-            outgoing.requestStop();
-        }
-    }
-
-    private class BottomStage extends Stage {
-        @Override
-        public void handleIncoming(T message) {
-            incoming.handleIncoming(message);
-        }
-
-        @Override
-        public void handleOutgoing(T message) {
-            outgoingQueue.dispatch(message);
-        }
-    }
-
-    private class BottomConnection implements AsyncConnection<T> {
-        public void dispatchTo(Dispatch<? super T> handler) {
-            outgoingQueue.dispatchTo(new FailureHandlingDispatch<T>(handler, outgoingDispatchFailureHandler));
-        }
-
-        public void dispatch(final T message) {
-            workQueue.dispatch(new Runnable() {
-                @Override
-                public String toString() {
-                    return String.format("incoming %s", message);
-                }
-
-                public void run() {
-                    stack.getLast().handleIncoming(message);
-                }
-            });
-        }
-    }
-
-    private class TopConnection implements AsyncConnection<T> {
-        public void dispatchTo(Dispatch<? super T> handler) {
-            incomingQueue.dispatchTo(new FailureHandlingDispatch<T>(handler, incomingDispatchFailureHandler));
-        }
-
-        public void dispatch(final T message) {
-            workQueue.dispatch(new Runnable() {
-                @Override
-                public String toString() {
-                    return String.format("outgoing %s", message);
-                }
-
-                public void run() {
-                    stack.getFirst().handleOutgoing(message);
-                }
-            });
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ReceiveProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ReceiveProtocol.java
deleted file mode 100644
index d13d874..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ReceiveProtocol.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.remote.internal.protocol.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.HashSet;
-import java.util.Set;
-
-public class ReceiveProtocol implements Protocol<Message> {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ReceiveProtocol.class);
-    private final Object id;
-    private final String displayName;
-    private final String channelKey;
-    private final Set<Object> producers = new HashSet<Object>();
-    private ProtocolContext<Message> context;
-    private boolean stopping;
-
-    public ReceiveProtocol(Object id, String displayName, String channelKey) {
-        this.id = id;
-        this.displayName = displayName;
-        this.channelKey = channelKey;
-    }
-
-    public void start(ProtocolContext<Message> context) {
-        this.context = context;
-        LOGGER.debug("Starting receiver {}.", id);
-        context.dispatchOutgoing(new ConsumerAvailable(id, displayName, channelKey));
-    }
-
-    public void handleIncoming(Message message) {
-        if (message instanceof ProducerReady) {
-            LOGGER.debug("Producer ready: {}", message);
-            ProducerReady producerReady = (ProducerReady) message;
-            producers.add(producerReady.getProducerId());
-            context.dispatchOutgoing(new ConsumerReady(id, producerReady.getProducerId()));
-        } else if (message instanceof ProducerStopped) {
-            LOGGER.debug("Producer stopped: {}", message);
-            ProducerStopped producerStopped = (ProducerStopped) message;
-            context.dispatchOutgoing(new ConsumerStopped(id, producerStopped.getProducerId()));
-            removeProducer(producerStopped.getProducerId());
-        } else if (message instanceof ProducerUnavailable) {
-            LOGGER.debug("Producer unavailable: {}", message);
-            ProducerUnavailable producerUnavailable = (ProducerUnavailable) message;
-            removeProducer(producerUnavailable.getId());
-        } else if (message instanceof ProducerAvailable) {
-            // Ignore these broadcasts
-            return;
-        } else if (message instanceof Request) {
-            context.dispatchIncoming(message);
-        } else {
-            throw new IllegalArgumentException(String.format("Unexpected incoming message received: %s", message));
-        }
-    }
-
-    private void removeProducer(Object producerId) {
-        producers.remove(producerId);
-        if (stopping && producers.isEmpty()) {
-            LOGGER.debug("All producers finished. Stopping now.");
-            allProducersFinished();
-        }
-    }
-
-    public void handleOutgoing(Message message) {
-        if (message instanceof WorkerStopping) {
-            workerStopped();
-        } else if (message instanceof MessageCredits) {
-            LOGGER.debug("Discarding {}.", message);
-        } else {
-            throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
-        }
-    }
-
-    private void workerStopped() {
-        stopping = true;
-        if (producers.isEmpty()) {
-            LOGGER.debug("No producers. Stopping now.");
-            allProducersFinished();
-            return;
-        }
-
-        LOGGER.debug("Waiting for producers to finish. Stopping later. Producers: {}", producers);
-        for (Object producer : producers) {
-            context.dispatchOutgoing(new ConsumerStopping(id, producer));
-        }
-    }
-
-    private void allProducersFinished() {
-        context.dispatchOutgoing(new ConsumerUnavailable(id));
-        context.dispatchIncoming(new EndOfStreamEvent());
-    }
-
-    public void stopRequested() {
-        assert stopping;
-        context.stopped();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SendProtocol.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SendProtocol.java
deleted file mode 100644
index 5f7abc6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SendProtocol.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.remote.internal.protocol.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-public class SendProtocol implements Protocol<Message> {
-    private static final Logger LOGGER = LoggerFactory.getLogger(SendProtocol.class);
-    private final String channelKey;
-    private final Object id;
-    private final String displayName;
-    private ProtocolContext<Message> context;
-    private boolean stopping;
-    private final Map<Object, ConsumerAvailable> pending = new HashMap<Object, ConsumerAvailable>();
-    private final Set<Object> consumers = new HashSet<Object>();
-
-    public SendProtocol(Object id, String displayName, String channelKey) {
-        this.channelKey = channelKey;
-        this.id = id;
-        this.displayName = displayName;
-    }
-
-    public void start(ProtocolContext<Message> context) {
-        LOGGER.debug("Starting producer {}", id);
-        this.context = context;
-        context.dispatchOutgoing(new ProducerAvailable(id, displayName, channelKey));
-    }
-
-    public void handleIncoming(Message message) {
-        if (message instanceof ConsumerAvailable) {
-            LOGGER.debug("Consumer available: {}", message);
-            ConsumerAvailable consumerAvailable = (ConsumerAvailable) message;
-            pending.put(consumerAvailable.getId(), consumerAvailable);
-            consumers.add(consumerAvailable.getId());
-            context.dispatchOutgoing(new ProducerReady(id, consumerAvailable.getId()));
-        } else if (message instanceof ConsumerReady) {
-            LOGGER.debug("Consumer ready: {}", message);
-            ConsumerReady consumerReady = (ConsumerReady) message;
-            context.dispatchIncoming(pending.remove(consumerReady.getConsumerId()));
-        } else if (message instanceof ConsumerStopping) {
-            LOGGER.debug("Consumer stopping: {}", message);
-            ConsumerStopping consumerStopping = (ConsumerStopping) message;
-            context.dispatchIncoming(new ConsumerUnavailable(consumerStopping.getConsumerId()));
-            context.dispatchOutgoing(new ProducerStopped(id, consumerStopping.getConsumerId()));
-        } else if (message instanceof ConsumerStopped) {
-            LOGGER.debug("Consumer stopped: {}", message);
-            ConsumerStopped consumerStopped = (ConsumerStopped) message;
-            consumers.remove(consumerStopped.getConsumerId());
-            maybeStop();
-        } else if (message instanceof ConsumerUnavailable) {
-            LOGGER.debug("Consumer unavailable: {}", message);
-            ConsumerUnavailable consumerUnavailable = (ConsumerUnavailable) message;
-            consumers.remove(consumerUnavailable.getId());
-            if (pending.remove(consumerUnavailable.getId()) == null) {
-                context.dispatchIncoming(new ConsumerUnavailable(consumerUnavailable.getId()));
-            }
-            maybeStop();
-        } else {
-            throw new IllegalArgumentException(String.format("Unexpected incoming message received: %s", message));
-        }
-    }
-
-    private void maybeStop() {
-        if (consumers.isEmpty() && stopping) {
-            LOGGER.debug("All consumers stopped. Stopping now.");
-            context.dispatchOutgoing(new ProducerUnavailable(id));
-            context.stopped();
-        }
-    }
-
-    public void handleOutgoing(Message message) {
-        if (message instanceof RoutableMessage) {
-            RoutableMessage routableMessage = (RoutableMessage) message;
-            if (!consumers.contains(routableMessage.getDestination())) {
-                throw new IllegalStateException(String.format("Message to unexpected destination dispatched: %s", message));
-            }
-            context.dispatchOutgoing(message);
-        } else {
-            throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
-        }
-    }
-
-    public void stopRequested() {
-        stopping = true;
-        if (consumers.isEmpty()) {
-            maybeStop();
-            return;
-        }
-
-        LOGGER.debug("Waiting for consumers to stop: {}", consumers);
-        context.stopLater();
-        for (Object consumerId : consumers) {
-            context.dispatchOutgoing(new ProducerStopped(id, consumerId));
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SynchronizedDispatch.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SynchronizedDispatch.java
deleted file mode 100644
index c219185..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/SynchronizedDispatch.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.api.internal.Operation;
-import org.gradle.api.internal.concurrent.Synchronizer;
-
-/**
- * Connection decorator that synchronizes dispatching.
- * <p>
- * by Szczepan Faber, created at: 2/27/12
- */
-public class SynchronizedDispatch<T> implements Connection<T> {
-    
-    private final Synchronizer sync = new Synchronizer();
-    private final Connection<T> delegate;
-
-    public SynchronizedDispatch(Connection<T> delegate) {
-        this.delegate = delegate;
-    }
-    
-    public void requestStop() {
-        delegate.requestStop();
-    }
-
-    public void dispatch(final T message) {
-        sync.synchronize(new Operation() {
-            public void execute() {
-                delegate.dispatch(message);
-            }
-        });
-    }
-
-    public T receive() {
-        //in case one wants to synchronize this method,
-        //bear in mind that it is blocking so it cannot share the same lock as others
-        return delegate.receive();
-    }
-
-    public void stop() {
-        delegate.stop();
-    }
-
-    public String toString() {
-        return delegate.toString();
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
deleted file mode 100644
index 9a04f14..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.inet;
-
-import org.gradle.api.GradleException;
-
-import java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-
-public class InetAddressFactory {
-    /**
-     * Locates the local (loopback) addresses for this machine. Never returns an empty list.
-     */
-    public List<InetAddress> findLocalAddresses() {
-        try {
-            List<InetAddress> addresses = new ArrayList<InetAddress>();
-            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
-            while (interfaces.hasMoreElements()) {
-                NetworkInterface networkInterface = interfaces.nextElement();
-                Enumeration<InetAddress> candidates = networkInterface.getInetAddresses();
-                while (candidates.hasMoreElements()) {
-                    InetAddress candidate = candidates.nextElement();
-                    if (candidate.isLoopbackAddress()) {
-                        addresses.add(candidate);
-                    }
-                }
-            }
-            if (addresses.isEmpty()) {
-                addresses.add(InetAddress.getByName(null));
-            }
-            return addresses;
-        } catch (Exception e) {
-            throw new GradleException("Could not determine the local IP addresses for this machine.", e);
-        }
-    }
-
-    /**
-     * Locates the remote (non-loopback) addresses for this machine. Never returns an empty list.
-     */
-    public List<InetAddress> findRemoteAddresses() {
-        try {
-            List<InetAddress> addresses = new ArrayList<InetAddress>();
-            Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
-            while (interfaces.hasMoreElements()) {
-                NetworkInterface networkInterface = interfaces.nextElement();
-                Enumeration<InetAddress> candidates = networkInterface.getInetAddresses();
-                while (candidates.hasMoreElements()) {
-                    InetAddress candidate = candidates.nextElement();
-                    if (!candidate.isLoopbackAddress()) {
-                        addresses.add(candidate);
-                    }
-                }
-            }
-            if (addresses.isEmpty()) {
-                addresses.add(InetAddress.getLocalHost());
-            }
-            return addresses;
-        } catch (Exception e) {
-            throw new GradleException("Could not determine the remote IP addresses for this machine.", e);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MulticastConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
deleted file mode 100644
index c1fee6a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.inet;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.MessageIOException;
-import org.gradle.messaging.remote.internal.MessageSerializer;
-
-import java.io.*;
-import java.net.DatagramPacket;
-import java.net.MulticastSocket;
-import java.net.SocketException;
-
-public class MulticastConnection<T> implements Connection<T> {
-    private static final int MAX_MESSAGE_SIZE = 32*1024;
-    private final MulticastSocket socket;
-    private final SocketInetAddress address;
-    private final MessageSerializer<T> serializer;
-    private final SocketInetAddress localAddress;
-
-    public MulticastConnection(SocketInetAddress address, MessageSerializer<T> serializer) {
-        this.address = address;
-        this.serializer = serializer;
-        try {
-            socket = new MulticastSocket(address.getPort());
-            socket.joinGroup(address.getAddress());
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        localAddress = new SocketInetAddress(socket.getInetAddress(), socket.getLocalPort());
-    }
-
-    @Override
-    public String toString() {
-        return String.format("multicast connection %s", address);
-    }
-
-    public void dispatch(T message) {
-        try {
-            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
-            serializer.write(message, dataOutputStream);
-            dataOutputStream.close();
-            byte[] buffer = outputStream.toByteArray();
-            socket.send(new DatagramPacket(buffer, buffer.length, address.getAddress(), address.getPort()));
-        } catch (Exception e) {
-            throw new MessageIOException(String.format("Could not write multi-cast message on %s.", address), e);
-        }
-    }
-
-    public T receive() {
-        try {
-            byte[] buffer = new byte[MAX_MESSAGE_SIZE];
-            DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
-            socket.receive(packet);
-            ByteArrayInputStream inputStream = new ByteArrayInputStream(packet.getData(), packet.getOffset(), packet.getLength());
-            DataInputStream dataInputStream = new DataInputStream(inputStream);
-            return serializer.read(dataInputStream, localAddress, new SocketInetAddress(packet.getAddress(), packet.getPort()));
-        } catch (SocketException e) {
-            // Assume closed
-            return null;
-        } catch (Exception e) {
-            throw new MessageIOException(String.format("Could not receive multi-cast message on %s", address), e);
-        }
-    }
-
-    public void requestStop() {
-        socket.close();
-    }
-
-    public void stop() {
-        requestStop();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketConnection.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketConnection.java
deleted file mode 100755
index b19ed92..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketConnection.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal.inet;
-
-import com.google.common.base.Objects;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.MessageIOException;
-import org.gradle.messaging.remote.internal.MessageSerializer;
-
-import java.io.*;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedSelectorException;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.SocketChannel;
-
-public class SocketConnection<T> implements Connection<T> {
-    private final SocketChannel socket;
-    private final SocketInetAddress localAddress;
-    private final SocketInetAddress remoteAddress;
-    private final MessageSerializer<T> serializer;
-    private final DataInputStream instr;
-    private final DataOutputStream outstr;
-
-    public SocketConnection(SocketChannel socket, MessageSerializer<T> serializer) {
-        this.socket = socket;
-        this.serializer = serializer;
-        try {
-            // NOTE: we use non-blocking IO as there is no reliable way when using blocking IO to shutdown reads while
-            // keeping writes active. For example, Socket.shutdownInput() does not work on Windows.
-            socket.configureBlocking(false);
-            outstr = new DataOutputStream(new SocketOutputStream(socket));
-            instr = new DataInputStream(new SocketInputStream(socket));
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-        InetSocketAddress localSocketAddress = (InetSocketAddress) socket.socket().getLocalSocketAddress();
-        localAddress = new SocketInetAddress(localSocketAddress.getAddress(), localSocketAddress.getPort());
-        InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
-        remoteAddress = new SocketInetAddress(remoteSocketAddress.getAddress(), remoteSocketAddress.getPort());
-    }
-
-    @Override
-    public String toString() {
-        return String.format("socket connection at %s with %s", localAddress, remoteAddress);
-    }
-
-    public Address getLocalAddress() {
-        return localAddress;
-    }
-
-    public Address getRemoteAddress() {
-        return remoteAddress;
-    }
-
-    public T receive() {
-        try {
-            return serializer.read(instr, localAddress, remoteAddress);
-        } catch (Exception e) {
-            if (isEndOfStream(e)) {
-                return null;
-            }
-            throw new MessageIOException(String.format("Could not read message from '%s'.", remoteAddress), e);
-        }
-    }
-
-    private boolean isEndOfStream(Exception e) {
-        if (e instanceof EOFException) {
-            return true;
-        }
-        if (e instanceof IOException) {
-            if (Objects.equal(e.getMessage(), "An existing connection was forcibly closed by the remote host")) {
-                return true;
-            }
-            if (Objects.equal(e.getMessage(), "An established connection was aborted by the software in your host machine")) {
-                return true;
-            }
-            if (Objects.equal(e.getMessage(), "Connection reset by peer")) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void dispatch(T message) {
-        try {
-            serializer.write(message, outstr);
-            outstr.flush();
-        } catch (Exception e) {
-            throw new MessageIOException(String.format("Could not write message %s to '%s'.", message, remoteAddress), e);
-        }
-    }
-
-    public void requestStop() {
-        new CompositeStoppable(instr).stop();
-    }
-
-    public void stop() {
-        new CompositeStoppable(instr, outstr, socket).stop();
-    }
-
-    private static class SocketInputStream extends InputStream {
-        private final Selector selector;
-        private final ByteBuffer buffer;
-        private final SocketChannel socket;
-        private final byte[] readBuffer = new byte[1];
-
-        public SocketInputStream(SocketChannel socket) throws IOException {
-            this.socket = socket;
-            selector = Selector.open();
-            socket.register(selector, SelectionKey.OP_READ);
-            buffer = ByteBuffer.allocateDirect(4096);
-            buffer.limit(0);
-        }
-
-        @Override
-        public int read() throws IOException {
-            int nread = read(readBuffer, 0, 1);
-            if (nread <= 0) {
-                return nread;
-            }
-            return readBuffer[0];
-        }
-
-        @Override
-        public int read(byte[] dest, int offset, int max) throws IOException {
-            if (max == 0) {
-                return 0;
-            }
-
-            if (buffer.remaining() == 0) {
-                try {
-                    selector.select();
-                } catch (ClosedSelectorException e) {
-                    return -1;
-                }
-                if (!selector.isOpen()) {
-                    return -1;
-                }
-
-                buffer.clear();
-                int nread = socket.read(buffer);
-                buffer.flip();
-
-                if (nread < 0) {
-                    return -1;
-                }
-            }
-
-            int count = Math.min(buffer.remaining(), max);
-            buffer.get(dest, offset, count);
-            return count;
-        }
-
-        @Override
-        public void close() throws IOException {
-            selector.close();
-        }
-    }
-
-    private static class SocketOutputStream extends OutputStream {
-        private final Selector selector;
-        private final SocketChannel socket;
-        private final ByteBuffer buffer;
-        private final byte[] writeBuffer = new byte[1];
-
-        public SocketOutputStream(SocketChannel socket) throws IOException {
-            this.socket = socket;
-            selector = Selector.open();
-            socket.register(selector, SelectionKey.OP_WRITE);
-            buffer = ByteBuffer.allocateDirect(4096);
-        }
-
-        @Override
-        public void write(int b) throws IOException {
-            writeBuffer[0] = (byte) b;
-            write(writeBuffer);
-        }
-
-        @Override
-        public void write(byte[] src, int offset, int max) throws IOException {
-            int remaining = max;
-            int currentPos = offset;
-            while (remaining > 0) {
-                int count = Math.min(remaining, buffer.remaining());
-                if (count > 0) {
-                    buffer.put(src, currentPos, count);
-                    remaining -= count;
-                    currentPos += count;
-                }
-                if (buffer.remaining() == 0) {
-                    flush();
-                }
-            }
-        }
-
-        @Override
-        public void flush() throws IOException {
-            buffer.flip();
-            while (buffer.remaining() > 0) {
-                selector.select();
-                if (!selector.isOpen()) {
-                    throw new EOFException();
-                }
-                socket.write(buffer);
-            }
-            buffer.clear();
-        }
-
-        @Override
-        public void close() throws IOException {
-            selector.close();
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
deleted file mode 100755
index 084b0a6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal.inet;
-
-import org.gradle.api.Action;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.ConnectEvent;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.IncomingConnector;
-import org.gradle.messaging.remote.internal.MessageSerializer;
-import org.gradle.util.IdGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-public class TcpIncomingConnector<T> implements IncomingConnector<T>, AsyncStoppable {
-    private static final Logger LOGGER = LoggerFactory.getLogger(TcpIncomingConnector.class);
-    private final StoppableExecutor executor;
-    private final MessageSerializer<T> serializer;
-    private final IdGenerator<?> idGenerator;
-    private final List<InetAddress> localAddresses;
-    private final List<InetAddress> remoteAddresses;
-    private final List<ServerSocketChannel> serverSockets = new CopyOnWriteArrayList<ServerSocketChannel>();
-
-    public TcpIncomingConnector(ExecutorFactory executorFactory, MessageSerializer<T> serializer, InetAddressFactory addressFactory, IdGenerator<?> idGenerator) {
-        this.serializer = serializer;
-        this.idGenerator = idGenerator;
-        this.executor = executorFactory.create("Incoming TCP Connector");
-
-        localAddresses = addressFactory.findLocalAddresses();
-        remoteAddresses = addressFactory.findRemoteAddresses();
-    }
-
-    public Address accept(Action<ConnectEvent<Connection<T>>> action, boolean allowRemote) {
-        ServerSocketChannel serverSocket;
-        int localPort;
-        try {
-            serverSocket = ServerSocketChannel.open();
-            serverSockets.add(serverSocket);
-            serverSocket.socket().bind(new InetSocketAddress(0));
-            localPort = serverSocket.socket().getLocalPort();
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-
-        Object id = idGenerator.generateId();
-        List<InetAddress> addresses = allowRemote ? remoteAddresses : localAddresses;
-        Address address = new MultiChoiceAddress(id, localPort, addresses);
-        LOGGER.debug("Listening on {}.", address);
-
-        executor.execute(new Receiver(serverSocket, action, allowRemote));
-        return address;
-    }
-
-    public void requestStop() {
-        new CompositeStoppable().add(serverSockets).stop();
-    }
-
-    public void stop() {
-        requestStop();
-        executor.stop();
-    }
-
-    private class Receiver implements Runnable {
-        private final ServerSocketChannel serverSocket;
-        private final Action<ConnectEvent<Connection<T>>> action;
-        private final boolean allowRemote;
-
-        public Receiver(ServerSocketChannel serverSocket, Action<ConnectEvent<Connection<T>>> action, boolean allowRemote) {
-            this.serverSocket = serverSocket;
-            this.action = action;
-            this.allowRemote = allowRemote;
-        }
-
-        public void run() {
-            try {
-                try {
-                    while (true) {
-                        SocketChannel socket = serverSocket.accept();
-                        InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
-                        if (!allowRemote && !localAddresses.contains(remoteSocketAddress.getAddress())) {
-                            LOGGER.error("Cannot accept connection from remote address {}.", remoteSocketAddress.getAddress());
-                            socket.close();
-                            continue;
-                        }
-
-                        SocketConnection<T> connection = new SocketConnection<T>(socket, serializer);
-                        Address localAddress = connection.getLocalAddress();
-                        Address remoteAddress = connection.getRemoteAddress();
-
-                        LOGGER.debug("Accepted connection from {} to {}.", remoteAddress, localAddress);
-                        action.execute(new ConnectEvent<Connection<T>>(connection, localAddress, remoteAddress));
-                    }
-                } catch (ClosedChannelException e) {
-                    // Ignore
-                } catch (Exception e) {
-                    LOGGER.error("Could not accept remote connection.", e);
-                }
-            } finally {
-                new CompositeStoppable(serverSocket).stop();
-                serverSockets.remove(serverSocket);
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
deleted file mode 100755
index d0ade1e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal.inet;
-
-import org.gradle.api.GradleException;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.internal.ConnectException;
-import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.messaging.remote.internal.MessageSerializer;
-import org.gradle.messaging.remote.internal.OutgoingConnector;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketException;
-import java.nio.channels.SocketChannel;
-import java.util.List;
-
-public class TcpOutgoingConnector<T> implements OutgoingConnector<T> {
-    private static final Logger LOGGER = LoggerFactory.getLogger(TcpOutgoingConnector.class);
-    private final MessageSerializer<T> serializer;
-
-    public TcpOutgoingConnector(MessageSerializer<T> serializer) {
-        this.serializer = serializer;
-    }
-
-    public Connection<T> connect(Address destinationAddress) {
-        if (!(destinationAddress instanceof InetEndpoint)) {
-            throw new IllegalArgumentException(String.format("Cannot create a connection to address of unknown type: %s.", destinationAddress));
-        }
-        InetEndpoint address = (InetEndpoint) destinationAddress;
-        LOGGER.debug("Attempting to connect to {}.", address);
-
-        // Try each address in turn. Not all of them are necessarily reachable (eg when socket option IPV6_V6ONLY
-        // is on - the default for debian and others), so we will try each of them until we can connect
-        List<InetAddress> candidateAddresses = address.getCandidates();
-
-        // Now try each address
-        try {
-            SocketException lastFailure = null;
-            for (InetAddress candidate : candidateAddresses) {
-                LOGGER.debug("Trying to connect to address {}.", candidate);
-                SocketChannel socketChannel;
-                try {
-                    socketChannel = SocketChannel.open(new InetSocketAddress(candidate, address.getPort()));
-                } catch (SocketException e) {
-                    LOGGER.debug("Cannot connect to address {}, skipping.", candidate);
-                    lastFailure = e;
-                    continue;
-                }
-                LOGGER.debug("Connected to address {}.", candidate);
-                return new SocketConnection<T>(socketChannel, serializer);
-            }
-            throw lastFailure;
-        } catch (java.net.ConnectException e) {
-            throw new ConnectException(String.format("Could not connect to server %s. Tried addresses: %s.",
-                    destinationAddress, candidateAddresses), e);
-        } catch (Exception e) {
-            throw new GradleException(String.format("Could not connect to server %s. Tried addresses: %s.",
-                    destinationAddress, candidateAddresses), e);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java
deleted file mode 100644
index 8befb6b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ConsumerAvailable extends ParticipantAvailable {
-    public ConsumerAvailable(Object id, String displayName, String channelKey) {
-        super(id, displayName, channelKey);
-    }
-
-    public RouteUnavailableMessage getUnavailableMessage() {
-        return new ConsumerUnavailable(getId());
-    }
-
-    public boolean acceptIncoming(RouteAvailableMessage message) {
-        if (message instanceof ProducerAvailable) {
-            ProducerAvailable producerAvailable = (ProducerAvailable) message;
-            return producerAvailable.getChannelKey().equals(getChannelKey());
-        }
-        return false;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java
deleted file mode 100644
index 6a46f1c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-import org.gradle.messaging.remote.internal.Message;
-
-public abstract class ConsumerMessage extends Message implements RoutableMessage {
-    protected final Object consumerId;
-    protected final Object producerId;
-
-    public ConsumerMessage(Object consumerId, Object producerId) {
-        this.producerId = producerId;
-        this.consumerId = consumerId;
-    }
-
-    public Object getConsumerId() {
-        return consumerId;
-    }
-
-    public Object getProducerId() {
-        return producerId;
-    }
-
-    public Object getDestination() {
-        return producerId;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o == null || o.getClass() != getClass()) {
-            return false;
-        }
-        ConsumerMessage other = (ConsumerMessage) o;
-        return consumerId.equals(other.consumerId) && producerId.equals(other.producerId);
-    }
-
-    @Override
-    public int hashCode() {
-        return consumerId.hashCode() ^ producerId.hashCode();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("[%s, consumerId: %s, producerId: %s]", getClass().getSimpleName(), consumerId, producerId);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java
deleted file mode 100644
index 6904eaf..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ConsumerReady extends ConsumerMessage {
-    public ConsumerReady(Object consumerId, Object producerId) {
-        super(consumerId, producerId);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java
deleted file mode 100644
index ffae984..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ConsumerStopped extends ConsumerMessage {
-    public ConsumerStopped(Object consumerId, Object producerId) {
-        super(consumerId, producerId);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java
deleted file mode 100644
index fe397a0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ConsumerStopping extends ConsumerMessage {
-    public ConsumerStopping(Object consumerId, Object producerId) {
-        super(consumerId, producerId);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java
deleted file mode 100644
index 88e4c6c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ConsumerUnavailable extends ParticipantUnavailable {
-    public ConsumerUnavailable(Object id) {
-        super(id);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java
deleted file mode 100644
index 3399118..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-import org.gradle.messaging.remote.internal.Message;
-
-public abstract class ParticipantAvailable extends Message implements RouteAvailableMessage {
-    private final String channelKey;
-    private final Object id;
-    private final String displayName;
-
-    public ParticipantAvailable(Object id, String displayName, String channelKey) {
-        this.id = id;
-        this.displayName = displayName;
-        this.channelKey = channelKey;
-    }
-
-    public Object getId() {
-        return id;
-    }
-
-    public String getDisplayName() {
-        return displayName;
-    }
-
-    public String getChannelKey() {
-        return channelKey;
-    }
-
-    public Object getSource() {
-        return id;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("[%s id: %s, displayName: %s, channel: %s]", getClass().getSimpleName(), id, displayName, channelKey);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o == null || o.getClass() != getClass()) {
-            return false;
-        }
-        ParticipantAvailable other = (ParticipantAvailable) o;
-        return id.equals(other.id) && displayName.equals(other.displayName) && channelKey.equals(other.channelKey);
-    }
-
-    @Override
-    public int hashCode() {
-        return id.hashCode() ^ displayName.hashCode() ^ channelKey.hashCode();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java
deleted file mode 100644
index 06965e1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-import org.gradle.messaging.remote.internal.Message;
-
-public class ParticipantUnavailable extends Message implements RouteUnavailableMessage {
-    private final Object id;
-
-    public ParticipantUnavailable(Object id) {
-        this.id = id;
-    }
-
-    public Object getId() {
-        return id;
-    }
-
-    public Object getSource() {
-        return id;
-    }
-
-    @Override
-    public String toString() {
-        return String.format("[%s id: %s]", getClass().getSimpleName(), id);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o == null || o.getClass() != getClass()) {
-            return false;
-        }
-        ParticipantUnavailable other = (ParticipantUnavailable) o;
-        return id.equals(other.id);
-    }
-
-    @Override
-    public int hashCode() {
-        return id.hashCode();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java
deleted file mode 100644
index 513dd65..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ProducerAvailable extends ParticipantAvailable {
-    public ProducerAvailable(Object id, String displayName, String channelKey) {
-        super(id, displayName, channelKey);
-    }
-
-    public RouteUnavailableMessage getUnavailableMessage() {
-        return new ProducerUnavailable(getId());
-    }
-
-    public boolean acceptIncoming(RouteAvailableMessage message) {
-        if (message instanceof ConsumerAvailable) {
-            ConsumerAvailable consumerAvailable = (ConsumerAvailable) message;
-            return consumerAvailable.getChannelKey().equals(getChannelKey());
-        }
-        return false;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java
deleted file mode 100644
index 8ad71d2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-import org.gradle.messaging.remote.internal.Message;
-
-public abstract class ProducerMessage extends Message implements RoutableMessage {
-    protected final Object producerId;
-    protected final Object consumerId;
-
-    public ProducerMessage(Object producerId, Object consumerId) {
-        this.consumerId = consumerId;
-        this.producerId = producerId;
-    }
-
-    public Object getConsumerId() {
-        return consumerId;
-    }
-
-    public Object getProducerId() {
-        return producerId;
-    }
-
-    public Object getDestination() {
-        return consumerId;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (o == null || o.getClass() != getClass()) {
-            return false;
-        }
-        ProducerMessage other = (ProducerMessage) o;
-        return consumerId.equals(other.consumerId) && producerId.equals(other.producerId);
-    }
-
-    @Override
-    public int hashCode() {
-        return consumerId.hashCode() ^ producerId.hashCode();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("[%s producerId: %s, consumerId: %s", getClass().getSimpleName(), producerId, consumerId);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerReady.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerReady.java
deleted file mode 100644
index 175cfc7..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerReady.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ProducerReady extends ProducerMessage {
-    public ProducerReady(Object producerId, Object consumerId) {
-        super(producerId, consumerId);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java
deleted file mode 100644
index 0bd2dd1..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ProducerStopped extends ProducerMessage {
-    public ProducerStopped(Object producerId, Object consumerId) {
-        super(producerId, consumerId);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java b/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java
deleted file mode 100644
index 29b9828..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
-
-public class ProducerUnavailable extends ParticipantUnavailable {
-    public ProducerUnavailable(Object id) {
-        super(id);
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
index dbf3347..9672afa 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
@@ -15,12 +15,13 @@
  */
 package org.gradle.process.internal;
 
-import org.apache.commons.io.output.CloseShieldOutputStream;
 import org.apache.commons.lang.StringUtils;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.process.BaseExecSpec;
+import org.gradle.process.internal.streams.SafeStreams;
+import org.gradle.process.internal.streams.StreamsForwarder;
+import org.gradle.process.internal.streams.StreamsHandler;
 
-import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
@@ -32,15 +33,20 @@ import java.util.List;
 public abstract class AbstractExecHandleBuilder extends DefaultProcessForkOptions implements BaseExecSpec {
     private OutputStream standardOutput;
     private OutputStream errorOutput;
-    private InputStream input = new ByteArrayInputStream(new byte[0]);
+    private InputStream input;
     private String displayName;
     private final List<ExecHandleListener> listeners = new ArrayList<ExecHandleListener>();
     boolean ignoreExitValue;
+    boolean redirectErrorStream;
+    private StreamsHandler streamsHandler;
+    private int timeoutMillis = Integer.MAX_VALUE;
+    protected boolean daemon;
 
     public AbstractExecHandleBuilder(FileResolver fileResolver) {
         super(fileResolver);
-        standardOutput = new CloseShieldOutputStream(System.out);
-        errorOutput = new CloseShieldOutputStream(System.err);
+        standardOutput = SafeStreams.systemOut();
+        errorOutput = SafeStreams.systemErr();
+        input = SafeStreams.emptyInput();
     }
 
     public abstract List<String> getAllArguments();
@@ -98,8 +104,9 @@ public abstract class AbstractExecHandleBuilder extends DefaultProcessForkOption
         return displayName == null ? String.format("command '%s'", getExecutable()) : displayName;
     }
 
-    public void setDisplayName(String displayName) {
+    public AbstractExecHandleBuilder setDisplayName(String displayName) {
         this.displayName = displayName;
+        return this;
     }
 
     public AbstractExecHandleBuilder listener(ExecHandleListener listener) {
@@ -109,14 +116,41 @@ public abstract class AbstractExecHandleBuilder extends DefaultProcessForkOption
         this.listeners.add(listener);
         return this;
     }
-    
+
     public ExecHandle build() {
         String executable = getExecutable();
         if (StringUtils.isEmpty(executable)) {
             throw new IllegalStateException("execCommand == null!");
         }
 
+        StreamsHandler effectiveHandler = getEffectiveStreamsHandler();
         return new DefaultExecHandle(getDisplayName(), getWorkingDir(), executable, getAllArguments(), getActualEnvironment(),
-                standardOutput, errorOutput, input, listeners);
+                effectiveHandler, listeners, redirectErrorStream, timeoutMillis, daemon);
+    }
+
+    private StreamsHandler getEffectiveStreamsHandler() {
+        StreamsHandler effectiveHandler;
+        if (this.streamsHandler != null) {
+            effectiveHandler = this.streamsHandler;
+        } else {
+            boolean shouldReadErrorStream = !redirectErrorStream;
+            effectiveHandler = new StreamsForwarder(standardOutput, errorOutput, input, shouldReadErrorStream);
+        }
+        return effectiveHandler;
+    }
+
+    public AbstractExecHandleBuilder streamsHandler(StreamsHandler streamsHandler) {
+        this.streamsHandler = streamsHandler;
+        return this;
+    }
+
+    public AbstractExecHandleBuilder redirectErrorStream() {
+        this.redirectErrorStream = true;
+        return this;
+    }
+
+    public AbstractExecHandleBuilder setTimeout(int timeoutMillis) {
+        this.timeoutMillis = timeoutMillis;
+        return this;
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
index 2370d52..a1bfd97 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
@@ -16,20 +16,18 @@
 
 package org.gradle.process.internal;
 
-import org.apache.commons.lang.StringUtils;
+import com.google.common.base.Joiner;
+
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.internal.UncheckedException;
-import org.gradle.listener.AsyncListenerBroadcast;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.listener.ListenerBroadcast;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
 import org.gradle.process.ExecResult;
 import org.gradle.process.internal.shutdown.ShutdownHookActionRegister;
+import org.gradle.process.internal.streams.StreamsHandler;
 
 import java.io.File;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -43,29 +41,23 @@ import java.util.concurrent.locks.ReentrantLock;
  *
  * <h3>State flows</h3>
  *
- * <p>The ExecHandle has very strict state control.
- * The following state flows are allowed:</p>
- *
- * Normal state flow:
- * <ul><li>INIT -> STARTED -> SUCCEEDED</li></ul>
- * Failure state flows:
  * <ul>
- * <li>INIT -> FAILED</li>
- * <li>INIT -> STARTED -> FAILED</li>
+ *   <li>INIT -> STARTED -> [SUCCEEDED|FAILED|ABORTED|DETACHED]</li>
+ *   <li>INIT -> FAILED</li>
+ *   <li>INIT -> STARTED -> DETACHED -> ABORTED</li>
  * </ul>
- * Aborted state flow:
- * <ul><li>INIT -> STARTED -> ABORTED</li></ul>
  *
  * State is controlled on all control methods:
  * <ul>
- * <li>{@link #start()} can only be called when the state is NOT {@link ExecHandleState#STARTED}</li>
- * <li>{@link #abort()} can only be called when the state is {@link ExecHandleState#STARTED}</li>
+ * <li>{@link #start()} allowed when state is INIT</li>
+ * <li>{@link #abort()} allowed when state is STARTED or DETACHED</li>
  * </ul>
  *
  * @author Tom Eyckmans
  */
-public class DefaultExecHandle implements ExecHandle {
+public class DefaultExecHandle implements ExecHandle, ProcessSettings {
     private static final Logger LOGGER = Logging.getLogger(DefaultExecHandle.class);
+
     private final String displayName;
     /**
      * The working directory of the process.
@@ -84,17 +76,17 @@ public class DefaultExecHandle implements ExecHandle {
      * The variables to set in the environment the executable is run in.
      */
     private final Map<String, String> environment;
-
-    private final OutputStream standardOutput;
-    private final OutputStream errorOutput;
-    private final InputStream standardInput;
+    private final StreamsHandler streamsHandler;
+    private final boolean redirectErrorStream;
+    private int timeoutMillis;
+    private boolean daemon;
 
     /**
      * Lock to guard all mutable state
      */
     private final Lock lock;
 
-    private final Condition stateChange;
+    private final Condition condition;
 
     private final StoppableExecutor executor;
 
@@ -115,22 +107,23 @@ public class DefaultExecHandle implements ExecHandle {
     private final ExecHandleShutdownHookAction shutdownHookAction;
 
     DefaultExecHandle(String displayName, File directory, String command, List<String> arguments,
-                      Map<String, String> environment, OutputStream standardOutput, OutputStream errorOutput,
-                      InputStream standardInput, List<ExecHandleListener> listeners) {
+                      Map<String, String> environment, StreamsHandler streamsHandler,
+                      List<ExecHandleListener> listeners, boolean redirectErrorStream, int timeoutMillis, boolean daemon) {
         this.displayName = displayName;
         this.directory = directory;
         this.command = command;
         this.arguments = arguments;
         this.environment = environment;
-        this.standardOutput = standardOutput;
-        this.errorOutput = errorOutput;
-        this.standardInput = standardInput;
+        this.streamsHandler = streamsHandler;
+        this.redirectErrorStream = redirectErrorStream;
+        this.timeoutMillis = timeoutMillis;
+        this.daemon = daemon;
         this.lock = new ReentrantLock();
-        this.stateChange = lock.newCondition();
+        this.condition = lock.newCondition();
         this.state = ExecHandleState.INIT;
         executor = new DefaultExecutorFactory().create(String.format("Run %s", displayName));
         shutdownHookAction = new ExecHandleShutdownHookAction(this);
-        broadcast = new AsyncListenerBroadcast<ExecHandleListener>(ExecHandleListener.class, executor);
+        broadcast = new ListenerBroadcast<ExecHandleListener>(ExecHandleListener.class);
         broadcast.addAll(listeners);
     }
 
@@ -142,6 +135,10 @@ public class DefaultExecHandle implements ExecHandle {
         return command;
     }
 
+    public boolean isDaemon() {
+        return daemon;
+    }
+
     @Override
     public String toString() {
         return displayName;
@@ -155,18 +152,6 @@ public class DefaultExecHandle implements ExecHandle {
         return Collections.unmodifiableMap(environment);
     }
 
-    public OutputStream getStandardOutput() {
-        return standardOutput;
-    }
-
-    public OutputStream getErrorOutput() {
-        return errorOutput;
-    }
-
-    public InputStream getStandardInput() {
-        return standardInput;
-    }
-
     public ExecHandleState getState() {
         lock.lock();
         try {
@@ -179,8 +164,9 @@ public class DefaultExecHandle implements ExecHandle {
     private void setState(ExecHandleState state) {
         lock.lock();
         try {
+            LOGGER.debug("Changing state to: {}", state);
             this.state = state;
-            stateChange.signalAll();
+            this.condition.signalAll();
         } finally {
             lock.unlock();
         }
@@ -195,56 +181,62 @@ public class DefaultExecHandle implements ExecHandle {
         }
     }
 
-    private void setEndStateInfo(ExecHandleState state, int exitCode, Throwable failureCause) {
+    private void setEndStateInfo(ExecHandleState newState, int exitValue, Throwable failureCause) {
         ShutdownHookActionRegister.removeAction(shutdownHookAction);
 
         ExecResultImpl result;
+        ExecHandleState currentState;
         lock.lock();
         try {
+            currentState = this.state;
             ExecException wrappedException = null;
             if (failureCause != null) {
-                if (this.state == ExecHandleState.STARTING) {
-                    wrappedException = new ExecException(String.format("A problem occurred starting %s.",
+                if (currentState == ExecHandleState.STARTING) {
+                    wrappedException = new ExecException(String.format("A problem occurred starting process '%s'",
                             displayName), failureCause);
                 } else {
                     wrappedException = new ExecException(String.format(
-                            "A problem occurred waiting for %s to complete.", displayName), failureCause);
+                            "A problem occurred waiting for process '%s' to complete.", displayName), failureCause);
                 }
             }
-            setState(state);
-            execResult = new ExecResultImpl(exitCode, wrappedException);
+            setState(newState);
+            execResult = new ExecResultImpl(exitValue, wrappedException);
             result = execResult;
         } finally {
             lock.unlock();
         }
 
-        LOGGER.debug("Process finished (code: {}) for {}.", exitCode, displayName);
+        LOGGER.info("Process '{}' finished with exit value {} (state: {})", displayName, exitValue, newState);
 
-        broadcast.getSource().executionFinished(this, result);
-        broadcast.stop();
+        if (currentState != ExecHandleState.DETACHED && newState != ExecHandleState.DETACHED) {
+            broadcast.getSource().executionFinished(this, result);
+        }
         executor.requestStop();
     }
 
     public ExecHandle start() {
-        ProcessParentingInitializer.intitialize();
+        LOGGER.info("Starting process '{}'. Working directory: {} Command: {}",
+                displayName, directory, command + ' ' + Joiner.on(' ').useForNull("null").join(arguments));
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("Environment for process '{}': {}", displayName, environment);
+        }
         lock.lock();
         try {
+            ProcessParentingInitializer.intitialize();
             if (!stateIn(ExecHandleState.INIT)) {
-                throw new IllegalStateException("already started!");
+                throw new IllegalStateException(String.format("Cannot start process '%s' because it has already been started", displayName));
             }
             setState(ExecHandleState.STARTING);
 
-            execResult = null;
-
-            execHandleRunner = new ExecHandleRunner(this, executor);
-
+            execHandleRunner = new ExecHandleRunner(this, streamsHandler);
             executor.execute(execHandleRunner);
 
-            while (getState() == ExecHandleState.STARTING) {
+            while(stateIn(ExecHandleState.STARTING)) {
+                LOGGER.debug("Waiting until process started: {}.", displayName);
                 try {
-                    stateChange.await();
+                    condition.await();
                 } catch (InterruptedException e) {
-                    throw new UncheckedException(e);
+                    //ok, wrapping up
                 }
             }
 
@@ -252,7 +244,7 @@ public class DefaultExecHandle implements ExecHandle {
                 execResult.rethrowFailure();
             }
 
-            LOGGER.debug("Started {}.", displayName);
+            LOGGER.info("Successfully started process '{}'", displayName);
         } finally {
             lock.unlock();
         }
@@ -265,18 +257,35 @@ public class DefaultExecHandle implements ExecHandle {
             if (state == ExecHandleState.SUCCEEDED) {
                 return;
             }
-            if (!stateIn(ExecHandleState.STARTED)) {
-                throw new IllegalStateException("not in started state!");
+            if (!stateIn(ExecHandleState.STARTED, ExecHandleState.DETACHED)) {
+                throw new IllegalStateException(String.format("Cannot abort process '%s' because it is not in started or detached state", displayName));
             }
-            this.execHandleRunner.stopWaiting();
+            this.execHandleRunner.abortProcess();
         } finally {
             lock.unlock();
         }
     }
 
     public ExecResult waitForFinish() {
+        lock.lock();
+        try {
+            while (!stateIn(ExecHandleState.SUCCEEDED, ExecHandleState.ABORTED, ExecHandleState.FAILED, ExecHandleState.DETACHED)) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    //ok, wrapping up...
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+
         executor.stop();
 
+        return result();
+    }
+
+    private ExecResult result() {
         lock.lock();
         try {
             execResult.rethrowFailure();
@@ -286,9 +295,12 @@ public class DefaultExecHandle implements ExecHandle {
         }
     }
 
+    void detached() {
+        setEndStateInfo(ExecHandleState.DETACHED, 0, null);
+    }
+
     void started() {
         ShutdownHookActionRegister.addAction(shutdownHookAction);
-
         setState(ExecHandleState.STARTED);
         broadcast.getSource().executionStarted(this);
     }
@@ -303,7 +315,7 @@ public class DefaultExecHandle implements ExecHandle {
 
     void aborted(int exitCode) {
         if (exitCode == 0) {
-            // This can happen on windows
+            // This can happen on Windows
             exitCode = -1;
         }
         setEndStateInfo(ExecHandleState.ABORTED, exitCode, null);
@@ -321,6 +333,18 @@ public class DefaultExecHandle implements ExecHandle {
         broadcast.remove(listener);
     }
 
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public boolean getRedirectErrorStream() {
+        return redirectErrorStream;
+    }
+
+    public int getTimeout() {
+        return timeoutMillis;
+    }
+
     private class ExecResultImpl implements ExecResult {
         private final int exitValue;
         private final ExecException failure;
@@ -336,7 +360,7 @@ public class DefaultExecHandle implements ExecHandle {
 
         public ExecResult assertNormalExitValue() throws ExecException {
             if (exitValue != 0) {
-                throw new ExecException(String.format("%s finished with (non-zero) exit value %d.", StringUtils.capitalize(displayName), exitValue));
+                throw new ExecException(String.format("Process '%s' finished with non-zero exit value %d", displayName, exitValue));
             }
             return this;
         }
@@ -347,5 +371,10 @@ public class DefaultExecHandle implements ExecHandle {
             }
             return this;
         }
+
+        @Override
+        public String toString() {
+            return "{exitValue=" + exitValue + ", failure=" + failure + "}";
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java
index dc93079..a187be8 100755
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java
@@ -1,101 +1,101 @@
-/*
- * Copyright 2010 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 org.gradle.process.internal;
-
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.FileSource;
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.process.ProcessForkOptions;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-public class DefaultProcessForkOptions implements ProcessForkOptions {
-    private final FileResolver resolver;
-    private Object executable;
-    private FileSource workingDir;
-    private final Map<String, Object> environment = new HashMap<String, Object>(Jvm.current().getInheritableEnvironmentVariables(System.getenv()));
-
-    public DefaultProcessForkOptions(FileResolver resolver) {
-        this.resolver = resolver;
-        workingDir = resolver.resolveLater(".");
-    }
-
-    protected FileResolver getResolver() {
-        return resolver;
-    }
-
-    public String getExecutable() {
-        return executable == null ? null : executable.toString();
-    }
-
-    public void setExecutable(Object executable) {
-        this.executable = executable;
-    }
-
-    public ProcessForkOptions executable(Object executable) {
-        setExecutable(executable);
-        return this;
-    }
-
-    public File getWorkingDir() {
-        return workingDir.get();
-    }
-
-    public void setWorkingDir(Object dir) {
-        this.workingDir = resolver.resolveLater(dir);
-    }
-
-    public ProcessForkOptions workingDir(Object dir) {
-        setWorkingDir(dir);
-        return this;
-    }
-
-    public Map<String, Object> getEnvironment() {
-        return environment;
-    }
-
-    public Map<String, String> getActualEnvironment() {
-        Map<String, String> actual = new HashMap<String, String>();
-        for (Map.Entry<String, Object> entry : environment.entrySet()) {
-            actual.put(entry.getKey(), String.valueOf(entry.getValue().toString()));
-        }
-        return actual;
-    }
-
-    public void setEnvironment(Map<String, ?> environmentVariables) {
-        environment.clear();
-        environment.putAll(environmentVariables);
-    }
-
-    public ProcessForkOptions environment(String name, Object value) {
-        environment.put(name, value);
-        return this;
-    }
-
-    public ProcessForkOptions environment(Map<String, ?> environmentVariables) {
-        environment.putAll(environmentVariables);
-        return this;
-    }
-
-    public ProcessForkOptions copyTo(ProcessForkOptions target) {
-        target.setExecutable(executable);
-        target.setWorkingDir(workingDir);
-        target.setEnvironment(environment);
-        return this;
-    }
-}
+/*
+ * Copyright 2010 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 org.gradle.process.internal;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.Factory;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.process.ProcessForkOptions;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultProcessForkOptions implements ProcessForkOptions {
+    private final FileResolver resolver;
+    private Object executable;
+    private Factory<File> workingDir;
+    private final Map<String, Object> environment = new HashMap<String, Object>(Jvm.current().getInheritableEnvironmentVariables(System.getenv()));
+
+    public DefaultProcessForkOptions(FileResolver resolver) {
+        this.resolver = resolver;
+        workingDir = resolver.resolveLater(".");
+    }
+
+    protected FileResolver getResolver() {
+        return resolver;
+    }
+
+    public String getExecutable() {
+        return executable == null ? null : executable.toString();
+    }
+
+    public void setExecutable(Object executable) {
+        this.executable = executable;
+    }
+
+    public ProcessForkOptions executable(Object executable) {
+        setExecutable(executable);
+        return this;
+    }
+
+    public File getWorkingDir() {
+        return workingDir.create();
+    }
+
+    public void setWorkingDir(Object dir) {
+        this.workingDir = resolver.resolveLater(dir);
+    }
+
+    public ProcessForkOptions workingDir(Object dir) {
+        setWorkingDir(dir);
+        return this;
+    }
+
+    public Map<String, Object> getEnvironment() {
+        return environment;
+    }
+
+    public Map<String, String> getActualEnvironment() {
+        Map<String, String> actual = new HashMap<String, String>();
+        for (Map.Entry<String, Object> entry : environment.entrySet()) {
+            actual.put(entry.getKey(), String.valueOf(entry.getValue().toString()));
+        }
+        return actual;
+    }
+
+    public void setEnvironment(Map<String, ?> environmentVariables) {
+        environment.clear();
+        environment.putAll(environmentVariables);
+    }
+
+    public ProcessForkOptions environment(String name, Object value) {
+        environment.put(name, value);
+        return this;
+    }
+
+    public ProcessForkOptions environment(Map<String, ?> environmentVariables) {
+        environment.putAll(environmentVariables);
+        return this;
+    }
+
+    public ProcessForkOptions copyTo(ProcessForkOptions target) {
+        target.setExecutable(executable);
+        target.setWorkingDir(workingDir);
+        target.setEnvironment(environment);
+        return this;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
index 8dd43ee..3323502 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
@@ -111,7 +111,7 @@ public class DefaultWorkerProcess implements WorkerProcess {
             while (connection == null && running) {
                 try {
                     if (!condition.awaitUntil(connectExpiry)) {
-                        throw new ExecException(String.format("Timeout waiting for %s to connect.", execHandle));
+                        throw new ExecException(String.format("Timeout after waiting %.1f seconds for %s to connect.", ((double) connectTimeout) / 1000, execHandle));
                     }
                 } catch (InterruptedException e) {
                     throw UncheckedException.throwAsUncheckedException(e);
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
index ac899d5..85b0f42 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
@@ -20,19 +20,21 @@ import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.internal.Factory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.MessagingServer;
 import org.gradle.process.internal.child.ApplicationClassesInIsolatedClassLoaderWorkerFactory;
 import org.gradle.process.internal.child.ApplicationClassesInSystemClassLoaderWorkerFactory;
+import org.gradle.process.internal.child.EncodedStream;
 import org.gradle.process.internal.child.WorkerFactory;
-import org.gradle.process.internal.launcher.GradleWorkerMain;
 import org.gradle.util.ClasspathUtil;
 import org.gradle.util.GUtil;
-import org.gradle.util.IdGenerator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
 import java.net.URL;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -63,7 +65,6 @@ public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder
         public DefaultWorkerProcessBuilder() {
             super(resolver);
             setLogLevel(workerLogLevel);
-            getJavaCommand().setMain(GradleWorkerMain.class.getName());
         }
 
         @Override
@@ -105,7 +106,10 @@ public class DefaultWorkerProcessFactory implements Factory<WorkerProcessBuilder
         }
 
         private void attachStdInContent(WorkerFactory workerFactory, JavaExecHandleBuilder javaCommand) {
-            ByteArrayInputStream stdinContent = new ByteArrayInputStream(GUtil.serialize(workerFactory.create()));
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            OutputStream encoded = new EncodedStream.EncodedOutput(bytes);
+            GUtil.serialize(workerFactory.create(), encoded);
+            ByteArrayInputStream stdinContent = new ByteArrayInputStream(bytes.toByteArray());
             javaCommand.setStandardInput(stdinContent);
         }
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
index 95eab48..9a999a1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
@@ -46,6 +46,11 @@ public interface ExecHandle {
 
     void abort();
 
+    /**
+     * Waits for the process to finish.
+     *
+     * @return result
+     */
     ExecResult waitForFinish();
 
     void addListener(ExecHandleListener listener);
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
index ca4ecf1..175d150 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
@@ -22,6 +22,9 @@ import org.gradle.api.internal.file.IdentityFileResolver;
 import org.gradle.process.ExecSpec;
 import org.gradle.util.GUtil;
 
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -40,12 +43,17 @@ public class ExecHandleBuilder extends AbstractExecHandleBuilder implements Exec
         super(fileResolver);
     }
 
+    public ExecHandleBuilder executable(Object executable) {
+        super.executable(executable);
+        return this;
+    }
+
     public ExecHandleBuilder commandLine(Object... arguments) {
         commandLine(Arrays.asList(arguments));
         return this;
     }
 
-    public ExecSpec commandLine(Iterable<?> args) {
+    public ExecHandleBuilder commandLine(Iterable<?> args) {
         List<Object> argsList = Lists.newArrayList(args);
         executable(argsList.get(0));
         setArgs(argsList.subList(1, argsList.size()));
@@ -68,7 +76,7 @@ public class ExecHandleBuilder extends AbstractExecHandleBuilder implements Exec
         return this;
     }
 
-    public ExecSpec args(Iterable<?> args) {
+    public ExecHandleBuilder args(Iterable<?> args) {
         GUtil.addToCollection(arguments, args);
         return this;
     }
@@ -97,4 +105,51 @@ public class ExecHandleBuilder extends AbstractExecHandleBuilder implements Exec
         super.setIgnoreExitValue(ignoreExitValue);
         return this;
     }
-}
+
+    @Override
+    public ExecHandleBuilder workingDir(Object dir) {
+        super.workingDir(dir);
+        return this;
+    }
+
+    @Override
+    public ExecHandleBuilder setDisplayName(String displayName) {
+        super.setDisplayName(displayName);
+        return this;
+    }
+
+    public ExecHandleBuilder noStandardInput() {
+        setStandardInput(new ByteArrayInputStream(new byte[0]));
+        return this;
+    }
+
+    public ExecHandleBuilder redirectErrorStream() {
+        super.redirectErrorStream();
+        return this;
+    }
+
+    public ExecHandleBuilder setStandardOutput(OutputStream outputStream) {
+        super.setStandardOutput(outputStream);
+        return this;
+    }
+
+    public ExecHandleBuilder setStandardInput(InputStream inputStream) {
+        super.setStandardInput(inputStream);
+        return this;
+    }
+
+    public ExecHandleBuilder listener(ExecHandleListener listener) {
+        super.listener(listener);
+        return this;
+    }
+
+    public ExecHandleBuilder setTimeout(int timeoutMillis) {
+        super.setTimeout(timeoutMillis);
+        return this;
+    }
+
+    public ExecHandleBuilder setDaemon(boolean daemon) {
+        super.daemon = daemon;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
index 527ea50..baac7dc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
@@ -16,86 +16,99 @@
 
 package org.gradle.process.internal;
 
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.util.DisconnectableInputStream;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.process.internal.streams.StreamsHandler;
 
-import java.io.InputStream;
-import java.util.concurrent.Executor;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * @author Tom Eyckmans
  */
 public class ExecHandleRunner implements Runnable {
     private static final Object START_LOCK = new Object();
+    private static final Logger LOGGER = Logging.getLogger(ExecHandleRunner.class);
+
     private final ProcessBuilderFactory processBuilderFactory;
     private final DefaultExecHandle execHandle;
-    private final Executor threadPool;
-    private final Object lock;
+    private final Lock lock = new ReentrantLock();
+
     private Process process;
     private boolean aborted;
+    private final StreamsHandler streamsHandler;
 
-    public ExecHandleRunner(DefaultExecHandle execHandle, Executor threadPool) {
+    public ExecHandleRunner(DefaultExecHandle execHandle, StreamsHandler streamsHandler) {
         if (execHandle == null) {
             throw new IllegalArgumentException("execHandle == null!");
         }
+        this.streamsHandler = streamsHandler;
         this.processBuilderFactory = new ProcessBuilderFactory();
         this.execHandle = execHandle;
-        this.lock = new Object();
-        this.threadPool = threadPool;
     }
 
-    public void stopWaiting() {
-        synchronized (lock) {
+    public void abortProcess() {
+        lock.lock();
+        try {
             aborted = true;
             if (process != null) {
+                LOGGER.debug("Abort requested. Destroying process: {}.", execHandle.getDisplayName());
                 process.destroy();
             }
+        } finally {
+            lock.unlock();
         }
     }
 
     public void run() {
         ProcessBuilder processBuilder = processBuilderFactory.createProcessBuilder(execHandle);
-        int exitCode;
         try {
-            ExecOutputHandleRunner standardOutputRunner;
-            ExecOutputHandleRunner errorOutputRunner;
-            ExecOutputHandleRunner standardInputRunner;
-            InputStream instr = new DisconnectableInputStream(execHandle.getStandardInput(), new DefaultExecutorFactory());
             Process process;
 
             // This big fat static lock is here for windows. When starting multiple processes concurrently, the stdout
             // and stderr streams for some of the processes get stuck
             synchronized (START_LOCK) {
                 process = processBuilder.start();
-
-                standardOutputRunner = new ExecOutputHandleRunner("read process standard output",
-                        process.getInputStream(), execHandle.getStandardOutput());
-                errorOutputRunner = new ExecOutputHandleRunner("read process error output", process.getErrorStream(),
-                        execHandle.getErrorOutput());
-                standardInputRunner = new ExecOutputHandleRunner("write process standard input",
-                        instr, process.getOutputStream());
+                streamsHandler.connectStreams(process, execHandle.getDisplayName());
             }
-            synchronized (lock) {
-                this.process = process;
-            }
-
-            threadPool.execute(standardInputRunner);
-            threadPool.execute(standardOutputRunner);
-            threadPool.execute(errorOutputRunner);
+            setProcess(process);
 
             execHandle.started();
 
-            exitCode = process.waitFor();
-            instr.close();
+            LOGGER.debug("waiting until streams are handled...");
+            streamsHandler.start();
+
+            if (execHandle.isDaemon()) {
+                streamsHandler.stop();
+                detached();
+            } else {
+                int exitValue = process.waitFor();
+                streamsHandler.stop();
+                completed(exitValue);
+            }
         } catch (Throwable t) {
             execHandle.failed(t);
-            return;
         }
+    }
 
+    private void setProcess(Process process) {
+        lock.lock();
+        try {
+            this.process = process;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void completed(int exitValue) {
         if (aborted) {
-            execHandle.aborted(exitCode);
+            execHandle.aborted(exitValue);
         } else {
-            execHandle.finished(exitCode);
+            execHandle.finished(exitValue);
         }
     }
+
+    private void detached() {
+        execHandle.detached();
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java
index 7bae379..783c337 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java
@@ -25,5 +25,6 @@ public enum ExecHandleState {
     STARTED,
     ABORTED,
     FAILED,
+    DETACHED,
     SUCCEEDED
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java
deleted file mode 100644
index e29a56b..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.process.internal;
-
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.internal.CompositeStoppable;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * @author Tom Eyckmans
- */
-public class ExecOutputHandleRunner implements Runnable {
-    private final static Logger LOGGER = Logging.getLogger(ExecOutputHandleRunner.class);
-
-    private final String displayName;
-    private final InputStream inputStream;
-    private final OutputStream outputStream;
-
-    public ExecOutputHandleRunner(String displayName, InputStream inputStream, OutputStream outputStream) {
-        this.displayName = displayName;
-        this.inputStream = inputStream;
-        this.outputStream = outputStream;
-    }
-
-    public void run() {
-        byte[] buffer = new byte[2048];
-        try {
-            while (true) {
-                int nread = inputStream.read(buffer);
-                if (nread < 0) {
-                    break;
-                }
-                outputStream.write(buffer, 0, nread);
-                outputStream.flush();
-            }
-            new CompositeStoppable(inputStream, outputStream).stop();
-        } catch (Throwable t) {
-            LOGGER.error(String.format("Could not %s.", displayName), t);
-        }
-    }
-
-    @Override
-    public String toString() {
-        return displayName;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java
index a5812aa..31e77d0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/JvmOptions.java
@@ -35,6 +35,7 @@ public class JvmOptions {
     private static final Pattern MIN_HEAP_PATTERN = Pattern.compile("-Xms(.+)");
     private static final Pattern MAX_HEAP_PATTERN = Pattern.compile("-Xmx(.+)");
     private static final Pattern BOOTSTRAP_PATTERN = Pattern.compile("-Xbootclasspath:(.+)");
+    private static final String FILE_ENCODING_KEY = "file.encoding";
 
     private final List<Object> extraJvmArgs = new ArrayList<Object>();
     private final Map<String, Object> systemProperties = new TreeMap<String, Object>();
@@ -83,7 +84,7 @@ public class JvmOptions {
 
     /**
      * @return the list of jvm args we manage explicitly, for example, max heaps size or file encoding.
-     *          The result is a subset of options returned by {@link #getAllImmutableJvmArgs()}
+     * The result is a subset of options returned by {@link #getAllImmutableJvmArgs()}
      */
     public List<String> getManagedJvmArgs() {
         List<String> args = new ArrayList<String>();
@@ -210,15 +211,23 @@ public class JvmOptions {
 
     public void setSystemProperties(Map<String, ?> properties) {
         systemProperties.clear();
-        systemProperties.putAll(properties);
+        systemProperties(properties);
     }
 
     public void systemProperties(Map<String, ?> properties) {
+        final Object fileEncoding = properties.remove(FILE_ENCODING_KEY);
+        if (fileEncoding != null) {
+            defaultCharacterEncoding = fileEncoding.toString();
+        }
         systemProperties.putAll(properties);
     }
 
     public void systemProperty(String name, Object value) {
-        systemProperties.put(name, value);
+        if (name.equals(FILE_ENCODING_KEY)) {
+            defaultCharacterEncoding = value.toString();
+        } else {
+            systemProperties.put(name, value);
+        }
     }
 
     public FileCollection getBootstrapClasspath() {
@@ -300,6 +309,7 @@ public class JvmOptions {
         target.setBootstrapClasspath(bootstrapClasspath);
         target.setEnableAssertions(assertionsEnabled);
         target.setDebug(debug);
+        target.setDefaultCharacterEncoding(defaultCharacterEncoding);
     }
 
     public static List<String> fromString(String input) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java
index d23db03..59e4aa4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java
@@ -16,12 +16,9 @@
 
 package org.gradle.process.internal;
 
-import org.slf4j.LoggerFactory;
-import org.slf4j.Logger;
-
-import java.util.Map;
-import java.util.List;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Creates a {@link java.lang.ProcessBuilder} based on a {@link ExecHandle}.
@@ -29,30 +26,18 @@ import java.util.ArrayList;
  * @author Tom Eyckmans
  */
 public class ProcessBuilderFactory {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ProcessBuilderFactory.class);
+    public ProcessBuilder createProcessBuilder(ProcessSettings processSettings) {
+        List<String> commandWithArguments = new ArrayList<String>();
+        commandWithArguments.add(processSettings.getCommand());
+        commandWithArguments.addAll(processSettings.getArguments());
 
-    public ProcessBuilder createProcessBuilder(ExecHandle execHandle) {
-        final List<String> commandWithArguments = new ArrayList<String>();
-        final String command = execHandle.getCommand();
-        commandWithArguments.add(command);
-        final List<String> arguments = execHandle.getArguments();
-        if (LOGGER.isDebugEnabled()) {
-            LOGGER.debug("creating process builder for {}", execHandle);
-            LOGGER.debug("in directory {}", execHandle.getDirectory());
-            int argumentIndex = 0;
-            for (String argument : arguments) {
-                LOGGER.debug("with argument#{} = {}", argumentIndex, argument);
-                argumentIndex++;
-            }
-        }
-        commandWithArguments.addAll(arguments);
-        
-        final ProcessBuilder processBuilder = new ProcessBuilder(commandWithArguments);
+        ProcessBuilder processBuilder = new ProcessBuilder(commandWithArguments);
+        processBuilder.directory(processSettings.getDirectory());
+        processBuilder.redirectErrorStream(processSettings.getRedirectErrorStream());
 
-        processBuilder.directory(execHandle.getDirectory());
-        final Map<String, String> environment = processBuilder.environment();
+        Map<String, String> environment = processBuilder.environment();
         environment.clear();
-        environment.putAll(execHandle.getEnvironment());
+        environment.putAll(processSettings.getEnvironment());
 
         return processBuilder;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java
index 1f3d0db..6d05dfc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessParentingInitializer.java
@@ -16,8 +16,10 @@
 
 package org.gradle.process.internal;
 
-import org.gradle.api.internal.Operation;
-import org.gradle.api.internal.concurrent.Synchronizer;
+import org.gradle.internal.concurrent.Synchronizer;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.jna.WindowsHandlesManipulator;
 import org.gradle.internal.os.OperatingSystem;
 
@@ -30,29 +32,57 @@ public class ProcessParentingInitializer {
 
     private static boolean initialized;
     private static Synchronizer synchronizer = new Synchronizer();
+    private static final Logger LOGGER = Logging.getLogger(ProcessParentingInitializer.class);
 
     /**
      * Initializes the current process so that it can be a well behaving parent.
      * <p>
-     * If the process has be already initialized then the method does nothing.
+     * If the process has been already initialized then the method does nothing.
      */
     public static void intitialize() {
+        ProcessParentingInitializer.intitialize(new Factory<Object>() {
+            public Object create() { return null; } //no op
+        });
+    }
+    
+    /**
+     * Initializes the current process so that it can be a well behaving parent.
+     * <p>
+     * Intended to solve an intermittent windows problem that made some child processes not quite detached in concurrent scenario.
+     * This method is useful when the child process is intended to live longer than the parent process (this process)
+     * *and* you would like to support thread safety. Use it if spawning the child processes may occur concurrently.
+     * <p>
+     * The operation parameter is intended to spawn the well behaving daemon process that closes its inputs/outputs.
+     * The operation should not wait for the daemon to finish - it should rather monitor the daemon for a little bit
+     * to make sure it started properly and then return or throw. For example, the operation can consume daemon outputs
+     * until the daemon closes them.
+     * <p>
+     * operation parameter is a Factory so that your spawning operation can return value should you need it.
+     * <p>
+     * If the parent process has been already initialized then the method simply executes the operation.
+     */
+    public static <T> T intitialize(final Factory<T> operation) {
         if (initialized) {
-            return;
+            return operation.create();
         }
-        synchronizer.synchronize(new Operation() {
-            public void execute() {
+        //TODO SF the interface can be deleted.
+        return synchronizer.synchronize(new Factory<T>() {
+            public T create() {
                 if (initialized) {
-                    return;
+                    return operation.create();
                 }
-                //even if initialization fails we don't want it to re-run
-                initialized = true;
-
-                //make sure the the children will be fully detached on windows:
-                if (OperatingSystem.current().isWindows()) {
-                    new WindowsHandlesManipulator().uninheritStandardStreams();
+                try {
+                    //make sure the the children will be fully detached on windows:
+                    if (OperatingSystem.current().isWindows()) {
+                        new WindowsHandlesManipulator().uninheritStandardStreams();
+                    }
+                    return operation.create();
+                } finally {
+                    //even if initialization fails we don't want it to re-run
+                    initialized = true;
+                    LOGGER.info("An attempt to initialize for well behaving parent process finished.");
                 }
             }
         });
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessSettings.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessSettings.java
new file mode 100644
index 0000000..f571230
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ProcessSettings.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 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 org.gradle.process.internal;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 4/20/12
+ */
+public interface ProcessSettings {
+
+    File getDirectory();
+
+    String getCommand();
+
+    List<String> getArguments();
+
+    Map<String, String> getEnvironment();
+
+    boolean getRedirectErrorStream();
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java
index 09a10a5..e815bfb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java
@@ -17,15 +17,15 @@
 package org.gradle.process.internal.child;
 
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.messaging.remote.Address;
 import org.gradle.process.JavaExecSpec;
 import org.gradle.process.internal.WorkerProcessBuilder;
-import org.gradle.util.GFileUtils;
+import org.gradle.process.internal.launcher.GradleWorkerMain;
 
 import java.net.URI;
 import java.net.URL;
 import java.util.Collection;
-import java.util.List;
 import java.util.concurrent.Callable;
 
 /**
@@ -70,11 +70,12 @@ public class ApplicationClassesInIsolatedClassLoaderWorkerFactory implements Wor
     }
 
     public void prepareJavaCommand(JavaExecSpec execSpec) {
+        execSpec.setMain(GradleWorkerMain.class.getName());
         execSpec.classpath(classPathRegistry.getClassPath("WORKER_PROCESS").getAsFiles());
     }
 
     public Callable<?> create() {
-        List<URI> applicationClassPath = GFileUtils.toURIs(processBuilder.getApplicationClasspath());
+        Collection<URI> applicationClassPath = new DefaultClassPath(processBuilder.getApplicationClasspath()).getAsURIs();
         ActionExecutionWorker injectedWorker = new ActionExecutionWorker(processBuilder.getWorker(), workerId,
                 displayName, serverAddress);
         ImplementationClassLoaderWorker worker = new ImplementationClassLoaderWorker(processBuilder.getLogLevel(),
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
index f402b76..c1f8fcc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
@@ -20,10 +20,13 @@ import com.google.common.io.ByteStreams;
 import com.google.common.io.InputSupplier;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 import org.gradle.messaging.remote.Address;
 import org.gradle.process.JavaExecSpec;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.process.internal.launcher.BootstrapClassLoaderWorker;
+import org.gradle.process.internal.launcher.GradleWorkerMain;
 import org.gradle.util.GUtil;
 
 import java.io.*;
@@ -54,6 +57,9 @@ import java.util.concurrent.Callable;
  * </pre>
  */
 public class ApplicationClassesInSystemClassLoaderWorkerFactory implements WorkerFactory {
+
+    private final static Logger LOGGER = Logging.getLogger(ApplicationClassesInSystemClassLoaderWorkerFactory.class);
+
     private final Object workerId;
     private final String displayName;
     private final WorkerProcessBuilder processBuilder;
@@ -73,22 +79,24 @@ public class ApplicationClassesInSystemClassLoaderWorkerFactory implements Worke
     }
 
     public void prepareJavaCommand(JavaExecSpec execSpec) {
+        execSpec.setMain("jarjar." + GradleWorkerMain.class.getName());
         execSpec.classpath(classPathRegistry.getClassPath("WORKER_MAIN").getAsFiles());
         Object requestedSecurityManager = execSpec.getSystemProperties().get("java.security.manager");
         if (requestedSecurityManager != null) {
             execSpec.systemProperty("org.gradle.security.manager", requestedSecurityManager);
         }
-        execSpec.systemProperty("java.security.manager", BootstrapSecurityManager.class.getName());
+        execSpec.systemProperty("java.security.manager", "jarjar." + BootstrapSecurityManager.class.getName());
         try {
-            ByteArrayOutputStream stdin = new ByteArrayOutputStream();
-            DataOutputStream outstr = new DataOutputStream(stdin);
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            DataOutputStream outstr = new DataOutputStream(new EncodedStream.EncodedOutput(bytes));
+            LOGGER.debug("Writing an application classpath to child process' standard input.");
             outstr.writeInt(processBuilder.getApplicationClasspath().size());
             for (File file : processBuilder.getApplicationClasspath()) {
                 outstr.writeUTF(file.getAbsolutePath());
             }
             outstr.close();
             final InputStream originalStdin = execSpec.getStandardInput();
-            InputStream input = ByteStreams.join(ByteStreams.newInputStreamSupplier(stdin.toByteArray()), new InputSupplier<InputStream>() {
+            InputStream input = ByteStreams.join(ByteStreams.newInputStreamSupplier(bytes.toByteArray()), new InputSupplier<InputStream>() {
                 public InputStream getInput() throws IOException {
                     return originalStdin;
                 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/BootstrapSecurityManager.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/BootstrapSecurityManager.java
index 5985a4c..a9067cf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/BootstrapSecurityManager.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/BootstrapSecurityManager.java
@@ -60,7 +60,7 @@ public class BootstrapSecurityManager extends SecurityManager {
             Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
             addUrlMethod.setAccessible(true);
 
-            DataInputStream inputStream = new DataInputStream(System.in);
+            DataInputStream inputStream = new DataInputStream(new EncodedStream.EncodedInput(System.in));
             int count = inputStream.readInt();
             StringBuilder classpathStr = new StringBuilder();
             for (int i = 0; i < count; i++) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/EncodedStream.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/EncodedStream.java
new file mode 100644
index 0000000..b516b8c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/EncodedStream.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 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 org.gradle.process.internal.child;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Provides Input/OutputStream implementations that are able to encode/decode using a simple algorithm (byte<->2 digit hex string(2 bytes)).
+ * Useful when streams are interpreted a text streams as it happens on IBM java for standard input.
+ * <p>
+ * by Szczepan Faber, created at: 5/25/12
+ */
+public abstract class EncodedStream {
+    private final static char[] HEX_DIGIT = new char[] {
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
+    public static class EncodedInput extends InputStream {
+
+        private InputStream delegate;
+
+        public EncodedInput(java.io.InputStream delegate) {
+            this.delegate = delegate;
+        }
+
+        public int read() throws IOException {
+            int byte1 = delegate.read();
+            if (byte1 < 0) {
+                return -1;
+            }
+            int byte2 = delegate.read();
+            if (byte2 < 0) {
+                throw new IOException("Unable to decode, expected 2 bytes but received only 1 byte. It seems the stream was not encoded correctly.");
+            }
+            return (hexToByte(byte1) << 4) | hexToByte(byte2);
+        }
+
+        public static int hexToByte(int s) throws IOException {
+            if (s >= '0' && s <= '9') {
+                return s - '0';
+            }
+            if (s >= 'a' && s <= 'f') {
+                return s - 'a' + 10;
+            }
+            throw new IOException(String.format("Unexpected value %s received. It seems the stream was not encoded correctly.", s));
+        }
+    }
+
+    public static class EncodedOutput extends OutputStream {
+
+        private final OutputStream delegate;
+
+        public EncodedOutput(OutputStream delegate) {
+            this.delegate = delegate;
+        }
+
+        public void write(int b) throws IOException {
+            delegate.write(HEX_DIGIT[(b >> 4) & 0x0f]);
+            delegate.write(HEX_DIGIT[b & 0x0f]);
+        }
+
+        @Override
+        public void flush() throws IOException {
+            delegate.flush();
+        }
+
+        @Override
+        public void close() throws IOException {
+            delegate.close();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java
index 3d20816..ca09880 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java
@@ -19,6 +19,7 @@ package org.gradle.process.internal.child;
 import org.gradle.api.Action;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.io.ClassLoaderObjectInputStream;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.util.*;
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
index 8b7147d..ae6448f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
@@ -17,7 +17,7 @@
 package org.gradle.process.internal.child;
 
 import org.gradle.api.Action;
-import org.gradle.util.ClassLoaderObjectInputStream;
+import org.gradle.internal.io.ClassLoaderObjectInputStream;
 
 import java.io.ByteArrayInputStream;
 import java.util.concurrent.Callable;
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
index 4d6ce45..4cfe4e9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
@@ -16,21 +16,32 @@
 
 package org.gradle.process.internal.child;
 
+import com.tonicsystems.jarjar.JarJarTask;
+import com.tonicsystems.jarjar.Rule;
+import org.apache.tools.ant.types.Resource;
+import org.apache.tools.ant.types.ResourceCollection;
+import org.apache.tools.ant.types.resources.URLResource;
 import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.ClassPathProvider;
 import org.gradle.api.internal.classpath.ModuleRegistry;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.PersistentCache;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.process.internal.launcher.BootstrapClassLoaderWorker;
 import org.gradle.process.internal.launcher.GradleWorkerMain;
-import org.gradle.util.ClassPath;
-import org.gradle.util.DefaultClassPath;
-import org.gradle.util.GFileUtils;
+import org.gradle.util.AntUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
-import java.util.Arrays;
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
 
 public class WorkerProcessClassPathProvider implements ClassPathProvider {
+    private static final Logger LOGGER = LoggerFactory.getLogger(WorkerProcessClassPathProvider.class);
     private final CacheRepository cacheRepository;
     private final ModuleRegistry moduleRegistry;
     private final Object lock = new Object();
@@ -49,6 +60,7 @@ public class WorkerProcessClassPathProvider implements ClassPathProvider {
             classpath = classpath.plus(moduleRegistry.getModule("gradle-core").getImplementationClasspath());
             classpath = classpath.plus(moduleRegistry.getModule("gradle-cli").getImplementationClasspath());
             classpath = classpath.plus(moduleRegistry.getModule("gradle-native").getImplementationClasspath());
+            classpath = classpath.plus(moduleRegistry.getModule("gradle-messaging").getImplementationClasspath());
             classpath = classpath.plus(moduleRegistry.getExternalModule("slf4j-api").getClasspath());
             classpath = classpath.plus(moduleRegistry.getExternalModule("logback-classic").getClasspath());
             classpath = classpath.plus(moduleRegistry.getExternalModule("logback-core").getClasspath());
@@ -59,8 +71,9 @@ public class WorkerProcessClassPathProvider implements ClassPathProvider {
             synchronized (lock) {
                 if (workerClassPath == null) {
                     PersistentCache cache = cacheRepository.cache("workerMain").withInitializer(new CacheInitializer()).open();
-                    workerClassPath = new DefaultClassPath(classesDir(cache));
+                    workerClassPath = new DefaultClassPath(jarFile(cache));
                 }
+                LOGGER.debug("Using worker process classpath: {}", workerClassPath);
                 return workerClassPath;
             }
         }
@@ -68,18 +81,77 @@ public class WorkerProcessClassPathProvider implements ClassPathProvider {
         return null;
     }
 
-    private static File classesDir(PersistentCache cache) {
-        return new File(cache.getBaseDir(), "classes");
+    private static File jarFile(PersistentCache cache) {
+        return new File(cache.getBaseDir(), "gradle-worker.jar");
     }
 
     private static class CacheInitializer implements Action<PersistentCache> {
         public void execute(PersistentCache cache) {
-            File classesDir = classesDir(cache);
-            for (Class<?> aClass : Arrays.asList(GradleWorkerMain.class, BootstrapClassLoaderWorker.class, BootstrapSecurityManager.class)) {
-                String fileName = aClass.getName().replace('.', '/') + ".class";
-                GFileUtils.copyURLToFile(WorkerProcessClassPathProvider.class.getClassLoader().getResource(fileName),
-                        new File(classesDir, fileName));
+            File jarFile = jarFile(cache);
+            LOGGER.debug("Generating worker process classes to {}.", jarFile);
+
+            URL currentClasspath = getClass().getProtectionDomain().getCodeSource().getLocation();
+            JarJarTask task = new JarJarTask();
+            task.setDestFile(jarFile);
+
+            final List<Resource> classResources = new ArrayList<Resource>();
+            List<Class<?>> renamedClasses = Arrays.asList(GradleWorkerMain.class,
+                    BootstrapSecurityManager.class,
+                    EncodedStream.EncodedInput.class);
+            List<Class<?>> classes = new ArrayList<Class<?>>();
+            classes.add(BootstrapClassLoaderWorker.class);
+            classes.addAll(renamedClasses);
+            for (Class<?> aClass : classes) {
+                final String fileName = aClass.getName().replace('.', '/') + ".class";
+
+                // Prefer the class from the same classpath as the current class. This is for the case where we're running in a test under an older
+                // version of Gradle, whose worker classes will be visible to us.
+                // TODO - remove this once we have upgraded to a wrapper with these changes in it
+                Enumeration<URL> resources;
+                try {
+                    resources = WorkerProcessClassPathProvider.class.getClassLoader().getResources(fileName);
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+                URL resource = null;
+                while (resources.hasMoreElements()) {
+                    URL url = resources.nextElement();
+                    resource = url;
+                    if (url.toString().startsWith(currentClasspath.toString())) {
+                        break;
+                    }
+                }
+                URLResource urlResource = new URLResource(resource) {
+                    @Override
+                    public synchronized String getName() {
+                        return fileName;
+                    }
+                };
+                classResources.add(urlResource);
+            }
+
+            task.add(new ResourceCollection() {
+                public Iterator iterator() {
+                    return classResources.iterator();
+                }
+
+                public int size() {
+                    return classResources.size();
+                }
+
+                public boolean isFilesystemOnly() {
+                    return true;
+                }
+            });
+
+            for (Class<?> renamedClass : renamedClasses) {
+                Rule rule = new Rule();
+                rule.setPattern(renamedClass.getName());
+                rule.setResult("jarjar. at 0");
+                task.addConfiguredRule(rule);
             }
+
+            AntUtil.execute(task);
         }
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/launcher/GradleWorkerMain.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/launcher/GradleWorkerMain.java
index 09044d2..ac23f74 100644
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/launcher/GradleWorkerMain.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/launcher/GradleWorkerMain.java
@@ -16,6 +16,8 @@
 
 package org.gradle.process.internal.launcher;
 
+import org.gradle.process.internal.child.EncodedStream;
+
 import java.io.ObjectInputStream;
 import java.util.concurrent.Callable;
 
@@ -25,7 +27,7 @@ import java.util.concurrent.Callable;
 public class GradleWorkerMain {
     public void run() throws Exception {
         // Read the main action from stdin and execute it
-        ObjectInputStream instr = new ObjectInputStream(System.in);
+        ObjectInputStream instr = new ObjectInputStream(new EncodedStream.EncodedInput(System.in));
         Callable<?> main = (Callable<?>) instr.readObject();
         main.call();
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/ExecOutputHandleRunner.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/ExecOutputHandleRunner.java
new file mode 100644
index 0000000..00bb5f2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/ExecOutputHandleRunner.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010 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 org.gradle.process.internal.streams;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.CompositeStoppable;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.Executor;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class ExecOutputHandleRunner implements Runnable {
+    private final static Logger LOGGER = Logging.getLogger(ExecOutputHandleRunner.class);
+
+    private final String displayName;
+    private final InputStream inputStream;
+    private final OutputStream outputStream;
+
+    public ExecOutputHandleRunner(String displayName, InputStream inputStream, OutputStream outputStream) {
+        this.displayName = displayName;
+        this.inputStream = inputStream;
+        this.outputStream = outputStream;
+    }
+
+    public void run() {
+        byte[] buffer = new byte[2048];
+        try {
+            while (true) {
+                int nread = inputStream.read(buffer);
+                if (nread < 0) {
+                    break;
+                }
+                outputStream.write(buffer, 0, nread);
+                outputStream.flush();
+            }
+            new CompositeStoppable(inputStream, outputStream).stop();
+        } catch (Throwable t) {
+            LOGGER.error(String.format("Could not %s.", displayName), t);
+        }
+    }
+
+    public void run(Executor executor) {
+        executor.execute(this);
+    }
+
+    public void closeInput() throws IOException {
+        inputStream.close();
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/SafeStreams.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/SafeStreams.java
new file mode 100644
index 0000000..d751b39
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/SafeStreams.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 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 org.gradle.process.internal.streams;
+
+import org.apache.commons.io.output.CloseShieldOutputStream;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * by Szczepan Faber, created at: 4/17/12
+ */
+public class SafeStreams {
+
+    public static OutputStream systemErr() {
+        return new CloseShieldOutputStream(System.err);
+    }
+
+    public static InputStream emptyInput() {
+        return new ByteArrayInputStream(new byte[0]);
+    }
+
+    public static OutputStream systemOut() {
+        return new CloseShieldOutputStream(System.out);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsForwarder.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsForwarder.java
new file mode 100644
index 0000000..14fd1b2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsForwarder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 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 org.gradle.process.internal.streams;
+
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.util.DisconnectableInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * by Szczepan Faber, created at: 4/17/12
+ */
+public class StreamsForwarder implements StreamsHandler {
+
+    private final OutputStream standardOutput;
+    private final OutputStream errorOutput;
+    private final InputStream input;
+    private final boolean readErrorStream;
+
+    private StoppableExecutor executor;
+    private ExecOutputHandleRunner standardOutputRunner;
+    private ExecOutputHandleRunner errorOutputRunner;
+    private ExecOutputHandleRunner standardInputRunner;
+
+    public StreamsForwarder(OutputStream standardOutput, OutputStream errorOutput, InputStream input, boolean readErrorStream) {
+        this.standardOutput = standardOutput;
+        this.errorOutput = errorOutput;
+        this.input = input;
+        this.readErrorStream = readErrorStream;
+    }
+
+    public void connectStreams(Process process, String processName) {
+        InputStream instr = new DisconnectableInputStream(input);
+
+        standardOutputRunner = new ExecOutputHandleRunner("read standard output of: " + processName,
+                process.getInputStream(), standardOutput);
+        errorOutputRunner = new ExecOutputHandleRunner("read error output of: " + processName, process.getErrorStream(),
+                errorOutput);
+        standardInputRunner = new ExecOutputHandleRunner("write standard input into: " + processName,
+                instr, process.getOutputStream());
+
+        this.executor = new DefaultExecutorFactory().create(String.format("Forward streams with process: %s", processName));
+    }
+
+    public void start() {
+        executor.execute(standardInputRunner);
+        if (readErrorStream) {
+            executor.execute(errorOutputRunner);
+        }
+        executor.execute(standardOutputRunner);
+    }
+
+    public void stop() {
+        try {
+            standardInputRunner.closeInput();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        executor.stop();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsHandler.java b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsHandler.java
new file mode 100644
index 0000000..f13d605
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/streams/StreamsHandler.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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 org.gradle.process.internal.streams;
+
+import org.gradle.internal.Stoppable;
+
+/**
+ * by Szczepan Faber, created at: 4/27/12
+ */
+public interface StreamsHandler extends Stoppable {
+
+    void start();
+
+    void connectStreams(Process process, String processName);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java
index 5e9ed4f..81228bd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileEventAdapter.java
@@ -28,7 +28,7 @@ import org.gradle.api.initialization.Settings;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.api.tasks.TaskState;
 import org.gradle.initialization.BuildRequestMetaData;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 /**
  * Adapts various events to build a {@link BuildProfile} model, and then notifies a {@link ReportGeneratingProfileListener} when the model is ready.
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java
index cfc430b..696db00 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/GlobalTestServices.java
@@ -16,6 +16,7 @@
 package org.gradle.testfixtures.internal;
 
 import org.gradle.internal.Factory;
+import org.gradle.internal.TrueTimeProvider;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.api.internal.project.GlobalServicesRegistry;
 import org.gradle.listener.DefaultListenerManager;
@@ -27,7 +28,6 @@ import org.gradle.logging.internal.DefaultProgressLoggerFactory;
 import org.gradle.logging.internal.DefaultStyledTextOutputFactory;
 import org.gradle.logging.internal.OutputEventListener;
 import org.gradle.logging.internal.ProgressListener;
-import org.gradle.util.TrueTimeProvider;
 
 public class GlobalTestServices extends GlobalServicesRegistry {
     public GlobalTestServices() {
@@ -52,5 +52,9 @@ public class GlobalTestServices extends GlobalServicesRegistry {
         protected StyledTextOutputFactory createStyledTextOutputFactory() {
             return new DefaultStyledTextOutputFactory(listenerManager.getBroadcaster(OutputEventListener.class), new TrueTimeProvider());
         }
+
+        protected TestOutputEventListener createStubOutputEventListener() {
+            return new TestOutputEventListener();
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
index 36cd074..e6cfd1f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/InMemoryCacheFactory.java
@@ -21,6 +21,7 @@ import org.gradle.internal.Factory;
 import org.gradle.api.internal.changedetection.InMemoryIndexedCache;
 import org.gradle.cache.*;
 import org.gradle.cache.internal.*;
+import org.gradle.messaging.serialize.Serializer;
 
 import java.io.File;
 import java.util.Collections;
@@ -50,11 +51,15 @@ public class InMemoryCacheFactory implements CacheFactory {
     }
 
     private static class NoOpFileLock extends AbstractFileAccess {
-        public <T> T readFromFile(Factory<? extends T> action) throws LockTimeoutException {
+        public <T> T readFile(Factory<? extends T> action) throws LockTimeoutException {
             return action.create();
         }
 
-        public void writeToFile(Runnable action) throws LockTimeoutException {
+        public void updateFile(Runnable action) throws LockTimeoutException {
+            action.run();
+        }
+
+        public void writeFile(Runnable action) throws LockTimeoutException {
             action.run();
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestOutputEventListener.java b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestOutputEventListener.java
new file mode 100644
index 0000000..d73633c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/testfixtures/internal/TestOutputEventListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 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 org.gradle.testfixtures.internal;
+
+import com.google.common.collect.Lists;
+
+import org.gradle.logging.internal.OutputEvent;
+import org.gradle.logging.internal.OutputEventListener;
+
+import java.util.List;
+
+public class TestOutputEventListener implements OutputEventListener {
+    private final List<OutputEvent> events = Lists.newArrayList();
+
+    public void onOutput(OutputEvent event) {
+        events.add(event);
+    }
+
+    public List<OutputEvent> getEvents() {
+        return events;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java b/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java
index 11b2f60..dc6cc76 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/BuildCommencedTimeProvider.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.util;
 
+import org.gradle.internal.TimeProvider;
+
 public class BuildCommencedTimeProvider implements TimeProvider {
     private final long fixedTime = System.currentTimeMillis();
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java
index 806cea7..084dc82 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderFactory.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.util;
 
+import org.gradle.internal.classpath.ClassPath;
+
 import java.net.URI;
 
 public interface ClassLoaderFactory {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderObjectInputStream.java b/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderObjectInputStream.java
deleted file mode 100644
index f0f1688..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ClassLoaderObjectInputStream.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectStreamClass;
-
-public class ClassLoaderObjectInputStream extends ObjectInputStream {
-    private final ClassLoader loader;
-
-    public ClassLoaderObjectInputStream(InputStream in, ClassLoader loader) throws IOException {
-        super(in);
-        this.loader = loader;
-    }
-
-    public ClassLoader getClassLoader() {
-        return loader;
-    }
-
-    @Override
-    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
-        try {
-            return loader.loadClass(desc.getName());
-        } catch (ClassNotFoundException e) {
-            return super.resolveClass(desc);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ClassPath.java b/subprojects/core/src/main/groovy/org/gradle/util/ClassPath.java
deleted file mode 100644
index c6146b5..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ClassPath.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.util;
-
-import java.io.File;
-import java.net.URI;
-import java.net.URL;
-import java.util.Collection;
-
-/**
- * An immutable classpath.
- */
-public interface ClassPath {
-    boolean isEmpty();
-
-    Collection<URI> getAsURIs();
-
-    Collection<File> getAsFiles();
-
-    Collection<URL> getAsURLs();
-
-    URL[] getAsURLArray();
-
-    ClassPath plus(Collection<File> classPath);
-    
-    ClassPath plus(ClassPath classPath);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/Clock.java b/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
index f9a2e37..078007f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Clock.java
@@ -16,6 +16,9 @@
 
 package org.gradle.util;
 
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.TrueTimeProvider;
+
 /**
  * @author Hans Dockter
  */
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java b/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java
index a112970..6d7c5d6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/CollectionUtils.java
@@ -16,8 +16,8 @@
 package org.gradle.util;
 
 import com.google.common.collect.Lists;
-import org.gradle.api.specs.Spec;
 import org.gradle.api.Transformer;
+import org.gradle.api.specs.Spec;
 
 import java.util.*;
 
@@ -72,4 +72,36 @@ public abstract class CollectionUtils {
         }
         return result;
     }
+    
+    public static <E> List<E> compact(List<E> list) {
+        boolean foundAtLeastOneNull = false;
+        List<E> compacted = null;
+        int i = 0;
+        
+        for (E element : list) {
+            if (element == null) {
+                if (!foundAtLeastOneNull) {
+                    compacted = new ArrayList<E>(list.size());
+                    if (i > 0) {
+                        compacted.addAll(list.subList(0, i));
+                    }
+                }
+                foundAtLeastOneNull = true;
+            } else if (foundAtLeastOneNull) {
+                compacted.add(element);
+            }
+            ++i;
+        }
+
+        return foundAtLeastOneNull ? compacted : list;
+    }
+
+    public static <C extends Collection<String>> C stringize(Iterable<?> source, C destination) {
+        return collect(source, destination, new ToStringTransformer());
+    }
+
+    public static List<String> stringize(List<?> source) {
+        return stringize(source, new ArrayList<String>(source.size()));
+    }
+
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/CompositeIdGenerator.java b/subprojects/core/src/main/groovy/org/gradle/util/CompositeIdGenerator.java
deleted file mode 100644
index e8007bf..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/CompositeIdGenerator.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.util;
-
-import java.io.Serializable;
-
-public class CompositeIdGenerator implements IdGenerator<Object> {
-    private final Object scope;
-    private final IdGenerator<?> generator;
-
-    public CompositeIdGenerator(Object scope, IdGenerator<?> generator) {
-        this.scope = scope;
-        this.generator = generator;
-    }
-
-    public Object generateId() {
-        return new CompositeId(scope, generator.generateId());
-    }
-    
-    private static class CompositeId implements Serializable {
-        private final Object scope;
-        private final Object id;
-
-        private CompositeId(Object scope, Object id) {
-            this.id = id;
-            this.scope = scope;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (o == null || o.getClass() != getClass()) {
-                return false;
-            }
-
-            CompositeId other = (CompositeId) o;
-            return other.id.equals(id) && other.scope.equals(scope);
-        }
-
-        @Override
-        public int hashCode() {
-            return scope.hashCode() ^ id.hashCode();
-        }
-
-        @Override
-        public String toString() {
-            return String.format("%s.%s", scope, id);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
index 567d581..aaa4013 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ConfigureUtil.java
@@ -31,11 +31,11 @@ import static org.apache.commons.collections.CollectionUtils.subtract;
  */
 public class ConfigureUtil {
 
-    public static <T> T configureByMap(Map<String, ?> properties, T delegate) {
+    public static <T> T configureByMap(Map<?, ?> properties, T delegate) {
         DynamicObject dynamicObject = DynamicObjectUtil.asDynamicObject(delegate);
 
-        for (Map.Entry<String, ?> entry : properties.entrySet()) {
-            String name = entry.getKey();
+        for (Map.Entry<?, ?> entry : properties.entrySet()) {
+            String name = entry.getKey().toString();
             Object value = entry.getValue();
 
             if (dynamicObject.hasProperty(name)) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java b/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java
index abda632..684e616 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassLoaderFactory.java
@@ -16,6 +16,8 @@
 package org.gradle.util;
 
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.service.ServiceLocator;
 
 import javax.xml.datatype.DatatypeFactory;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -24,16 +26,18 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
 import java.net.URLClassLoader;
-import java.util.List;
+import java.util.Collection;
 
 public class DefaultClassLoaderFactory implements ClassLoaderFactory {
-    public ClassLoader createIsolatedClassLoader(ClassPath classPath) {
-        return createIsolatedClassLoader(classPath.getAsURIs());
+    public ClassLoader createIsolatedClassLoader(Iterable<URI> uris) {
+        return doCreateIsolatedClassLoader(GFileUtils.urisToUrls(uris));
     }
 
-    public ClassLoader createIsolatedClassLoader(Iterable<URI> uris) {
-        List<URL> classpath = GFileUtils.urisToUrls(uris);
+    public ClassLoader createIsolatedClassLoader(ClassPath classPath) {
+        return doCreateIsolatedClassLoader(classPath.getAsURLs());
+    }
 
+    private ClassLoader doCreateIsolatedClassLoader(Collection<URL> classpath) {
         // This piece of ugliness copies the JAXP (ie XML API) provider, if any, from the system ClassLoader. Here's why:
         //
         // 1. When looking for a provider, JAXP looks for a service resource in the context ClassLoader, which is our isolated ClassLoader. If our classpath above does not contain a
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassPath.java b/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassPath.java
deleted file mode 100644
index bba4217..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/DefaultClassPath.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.util;
-
-import java.io.File;
-import java.io.Serializable;
-import java.net.URI;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-public class DefaultClassPath implements ClassPath, Serializable {
-    private final List<File> files;
-
-    public DefaultClassPath(Iterable<File> files) {
-        this.files = new ArrayList<File>();
-        for (File file : files) {
-            this.files.add(file);
-        }
-    }
-    
-    public DefaultClassPath(File... files) {
-        this(Arrays.asList(files));
-    }
-
-    public boolean isEmpty() {
-        return files.isEmpty();
-    }
-
-    public Collection<URI> getAsURIs() {
-        return GFileUtils.toURIs(files);
-    }
-
-    public Collection<File> getAsFiles() {
-        return files;
-    }
-
-    public URL[] getAsURLArray() {
-        return GFileUtils.toURLArray(files);
-    }
-
-    public Collection<URL> getAsURLs() {
-        return GFileUtils.toURLs(files);
-    }
-
-    public ClassPath plus(ClassPath other) {
-        if (files.isEmpty()) {
-            return other;
-        }
-        if (other.isEmpty()) {
-            return this;
-        }
-        return new DefaultClassPath(concat(files, other.getAsFiles()));
-    }
-
-    public ClassPath plus(Collection<File> other) {
-        if (other.isEmpty()) {
-            return this;
-        }
-        return new DefaultClassPath(concat(files, other));
-    }
-
-    private Iterable<File> concat(List<File> files1, Collection<File> files2) {
-        List<File> result = new ArrayList<File>();
-        result.addAll(files1);
-        result.addAll(files2);
-        return result;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java b/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java
index d2e805b..d75d988 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DisconnectableInputStream.java
@@ -16,7 +16,8 @@
 package org.gradle.util;
 
 import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -37,11 +38,21 @@ public class DisconnectableInputStream extends BulkReadInputStream {
     private boolean closed;
     private boolean inputFinished;
 
-    public DisconnectableInputStream(final InputStream source, ExecutorFactory executorFactory) {
+    public DisconnectableInputStream(InputStream source) {
+        // We use our own executor factory, as there is no way to break a worker thread out of source.read() below
+        this(source, new DefaultExecutorFactory(), 1024);
+    }
+
+    public DisconnectableInputStream(InputStream source, int bufferLength) {
+        // We use our own executor factory, as there is no way to break a worker thread out of source.read() below
+        this(source, new DefaultExecutorFactory(), bufferLength);
+    }
+
+    DisconnectableInputStream(InputStream source, ExecutorFactory executorFactory) {
         this(source, executorFactory, 1024);
     }
 
-    public DisconnectableInputStream(final InputStream source, ExecutorFactory executorFactory, int bufferLength) {
+    DisconnectableInputStream(final InputStream source, ExecutorFactory executorFactory, int bufferLength) {
         buffer = new byte[bufferLength];
         executorFactory.create("read input").execute(new Runnable() {
             public void run() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
index 71f515f..ab2b12a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2012 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.
@@ -35,6 +35,7 @@ public class FilteringClassLoader extends ClassLoader {
     private final Set<String> resourcePrefixes = new HashSet<String>();
     private final Set<String> resourceNames = new HashSet<String>();
     private final Set<String> classNames = new HashSet<String>();
+    private final Set<String> disallowedClassNames = new HashSet<String>();
 
     static {
         EXT_CLASS_LOADER = ClassLoader.getSystemClassLoader().getParent();
@@ -123,31 +124,34 @@ public class FilteringClassLoader extends ClassLoader {
         return false;
     }
 
-    private boolean allowed(Package p) {
-        if (SYSTEM_PACKAGES.contains(p.getName())) {
+    private boolean allowed(Package pkg) {
+        if (SYSTEM_PACKAGES.contains(pkg.getName())) {
             return true;
         }
-        if (packageNames.contains(p.getName())) {
+        if (packageNames.contains(pkg.getName())) {
             return true;
         }
         for (String packagePrefix : packagePrefixes) {
-            if (p.getName().startsWith(packagePrefix)) {
+            if (pkg.getName().startsWith(packagePrefix)) {
                 return true;
             }
         }
         return false;
     }
 
-    private boolean allowed(final Class<?> cl) {
+    private boolean allowed(final Class<?> clazz) {
         boolean systemClass = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
             public Boolean run() {
-                return cl.getClassLoader() == null || SYSTEM_CLASS_LOADERS.contains(cl.getClassLoader());
+                return clazz.getClassLoader() == null || SYSTEM_CLASS_LOADERS.contains(clazz.getClassLoader());
             }
         });
-        return systemClass || classAllowed(cl.getName());
+        return systemClass || classAllowed(clazz.getName());
     }
 
     private boolean classAllowed(String className) {
+        if (disallowedClassNames.contains(className)) {
+            return false;
+        }
         if (classNames.contains(className)) {
             return true;
         }
@@ -162,7 +166,7 @@ public class FilteringClassLoader extends ClassLoader {
     /**
      * Marks a package and all its sub-packages as visible. Also makes resources in those packages visible.
      *
-     * @param packageName The package name
+     * @param packageName the package name
      */
     public void allowPackage(String packageName) {
         packageNames.add(packageName);
@@ -171,27 +175,36 @@ public class FilteringClassLoader extends ClassLoader {
     }
 
     /**
-     * Marks a single class as visible
+     * Marks a single class as visible.
+     *
+     * @param clazz the class
+     */
+    public void allowClass(Class<?> clazz) {
+        classNames.add(clazz.getName());
+    }
+
+    /**
+     * Marks a single class as not visible.
      *
-     * @param aClass The class
+     * @param className the class name
      */
-    public void allowClass(Class<?> aClass) {
-        classNames.add(aClass.getName());
+    public void disallowClass(String className) {
+        disallowedClassNames.add(className);
     }
 
     /**
      * Marks all resources with the given prefix as visible.
      *
-     * @param resourcePrefix The resource prefix.
+     * @param resourcePrefix the resource prefix
      */
     public void allowResources(String resourcePrefix) {
         resourcePrefixes.add(resourcePrefix + "/");
     }
 
     /**
-     * Marks a single resource as visible
+     * Marks a single resource as visible.
      *
-     * @param resourceName The resource name
+     * @param resourceName the resource name
      */
     public void allowResource(String resourceName) {
         resourceNames.add(resourceName);
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
index 60298e4..fd26344 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GFileUtils.java
@@ -17,7 +17,6 @@ package org.gradle.util;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.LineIterator;
 import org.apache.commons.io.filefilter.IOFileFilter;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.util.internal.LimitedDescription;
@@ -26,7 +25,10 @@ import java.io.*;
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
-import java.util.*;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 import java.util.zip.Checksum;
 
 /**
@@ -34,8 +36,6 @@ import java.util.zip.Checksum;
  */
 public class GFileUtils {
 
-    public static final String FILE_SEPARATOR = System.getProperty("file.separator");
-
     public static FileInputStream openInputStream(File file) {
         try {
             return FileUtils.openInputStream(file);
@@ -44,73 +44,44 @@ public class GFileUtils {
         }
     }
 
-    public static FileOutputStream openOutputStream(File file) {
+    public static void touch(File file) {
         try {
-            return FileUtils.openOutputStream(file);
+            FileUtils.touch(file);
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
     }
 
-    public static String byteCountToDisplaySize(long size) {
-        return FileUtils.byteCountToDisplaySize(size);
+    public static String readFile(File file) {
+        return readFile(file, Charset.defaultCharset().name());
     }
 
-    public static void touch(File file) {
+    public static String readFile(File file, String encoding) {
         try {
-            FileUtils.touch(file);
+            return FileUtils.readFileToString(file, encoding);
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
     }
 
-    public static File[] convertFileCollectionToFileArray(Collection files) {
-        return FileUtils.convertFileCollectionToFileArray(files);
-    }
-
-    public static Collection listFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
-        return FileUtils.listFiles(directory, fileFilter, dirFilter);
-    }
-
-    public static Iterator iterateFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
-        return FileUtils.iterateFiles(directory, fileFilter, dirFilter);
-    }
-
-    public static Collection listFiles(File directory, String[] extensions, boolean recursive) {
-        return FileUtils.listFiles(directory, extensions, recursive);
-    }
-
-    public static Iterator iterateFiles(File directory, String[] extensions, boolean recursive) {
-        return FileUtils.iterateFiles(directory, extensions, recursive);
+    public static void writeFile(String content, File destination) {
+        writeFile(content, destination, Charset.defaultCharset().name());
     }
 
-    public static boolean contentEquals(File file1, File file2) {
+    public static void writeFile(String content, File destination, String encoding) {
         try {
-            return FileUtils.contentEquals(file1, file2);
+            FileUtils.writeStringToFile(destination, content, encoding);
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
     }
 
-    public static File toFile(String... filePathParts) {
-        final StringWriter filePathBuffer = new StringWriter();
-
-        for (int i = 0; i < filePathParts.length; i++) {
-            filePathBuffer.write(filePathParts[i]);
-            if (i + 1 < filePathParts.length) {
-                filePathBuffer.write(FILE_SEPARATOR);
-            }
-        }
-
-        return new File(filePathBuffer.toString());
-    }
-
-    public static File toFile(URL url) {
-        return FileUtils.toFile(url);
+    public static Collection listFiles(File directory, IOFileFilter fileFilter, IOFileFilter dirFilter) {
+        return FileUtils.listFiles(directory, fileFilter, dirFilter);
     }
 
-    public static File[] toFiles(URL[] urls) {
-        return FileUtils.toFiles(urls);
+    public static Collection listFiles(File directory, String[] extensions, boolean recursive) {
+        return FileUtils.listFiles(directory, extensions, recursive);
     }
 
     public static List<String> toPaths(Collection<File> files) {
@@ -121,26 +92,6 @@ public class GFileUtils {
         return paths;
     }
 
-    public static List<URI> toURIs(Iterable<File> files) {
-        List<URI> urls = new ArrayList<URI>();
-        for (File file : files) {
-            urls.add(file.toURI());
-        }
-        return urls;
-    }
-
-    public static List<URL> toURLs(Iterable<File> files) {
-        List<URL> urls = new ArrayList<URL>();
-        for (File file : files) {
-            try {
-                urls.add(file.toURI().toURL());
-            } catch (MalformedURLException e) {
-                throw new UncheckedIOException(e);
-            }
-        }
-        return urls;
-    }
-
     public static List<URL> urisToUrls(Iterable<URI> uris) {
         List<URL> urls = new ArrayList<URL>();
         for (URI uri : uris) {
@@ -153,95 +104,6 @@ public class GFileUtils {
         return urls;
     }
 
-    public static URL[] toURLArray(Collection<File> files) {
-        return toURLs(files.toArray(new File[files.size()]));
-    }
-
-    public static URL[] toURLs(File[] files) {
-        try {
-            URL[] urls = new URL[files.length];
-            for (int i = 0; i < files.length; i++) {
-                File file = files[i];
-                urls[i] = file.toURI().toURL();
-            }
-            return urls;
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyFileToDirectory(File srcFile, File destDir) {
-        try {
-            FileUtils.copyFileToDirectory(srcFile, destDir);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) {
-        try {
-            FileUtils.copyFileToDirectory(srcFile, destDir, preserveFileDate);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyFile(File srcFile, File destFile) {
-        try {
-            FileUtils.copyFile(srcFile, destFile);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyFile(File srcFile, File destFile, boolean preserveFileDate) {
-        try {
-            FileUtils.copyFile(srcFile, destFile, preserveFileDate);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyDirectoryToDirectory(File srcDir, File destDir) {
-        try {
-            FileUtils.copyDirectoryToDirectory(srcDir, destDir);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyDirectory(File srcDir, File destDir) {
-        try {
-            FileUtils.copyDirectory(srcDir, destDir);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyDirectory(File srcDir, File destDir, boolean preserveFileDate) {
-        try {
-            FileUtils.copyDirectory(srcDir, destDir, preserveFileDate);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyDirectory(File srcDir, File destDir, FileFilter filter) {
-        try {
-            FileUtils.copyDirectory(srcDir, destDir, filter);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void copyDirectory(File srcDir, File destDir, FileFilter filter, boolean preserveFileDate) {
-        try {
-            FileUtils.copyDirectory(srcDir, destDir, filter, preserveFileDate);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
     public static void copyURLToFile(URL source, File destination) {
         try {
             FileUtils.copyURLToFile(source, destination);
@@ -262,58 +124,6 @@ public class GFileUtils {
         return FileUtils.deleteQuietly(file);
     }
 
-    public static void cleanDirectory(File directory) {
-        try {
-            FileUtils.cleanDirectory(directory);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static boolean waitFor(File file, int seconds) {
-        return FileUtils.waitFor(file, seconds);
-    }
-
-    public static String readFileToString(File file, String encoding) {
-        try {
-            return FileUtils.readFileToString(file, encoding);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static String readFileToString(File file) {
-        try {
-            return FileUtils.readFileToString(file);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static byte[] readFileToByteArray(File file) {
-        try {
-            return FileUtils.readFileToByteArray(file);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static List readLines(File file, String encoding) {
-        try {
-            return FileUtils.readLines(file, encoding);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static List readLines(File file) {
-        try {
-            return FileUtils.readLines(file);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
     public static class TailReadingException extends RuntimeException {
         public TailReadingException(Throwable throwable) {
             super(throwable);
@@ -348,30 +158,6 @@ public class GFileUtils {
         }
     }
 
-    public static LineIterator lineIterator(File file, String encoding) {
-        try {
-            return FileUtils.lineIterator(file, encoding);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static LineIterator lineIterator(File file) {
-        try {
-            return FileUtils.lineIterator(file);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void writeStringToFile(File file, String data, String encoding) {
-        try {
-            FileUtils.writeStringToFile(file, data, encoding);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
     public static void writeStringToFile(File file, String data) {
         try {
             FileUtils.writeStringToFile(file, data);
@@ -380,46 +166,6 @@ public class GFileUtils {
         }
     }
 
-    public static void writeByteArrayToFile(File file, byte[] data) {
-        try {
-            FileUtils.writeByteArrayToFile(file, data);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void writeLines(File file, String encoding, Collection lines) {
-        try {
-            FileUtils.writeLines(file, encoding, lines);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void writeLines(File file, Collection lines) {
-        try {
-            FileUtils.writeLines(file, lines);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void writeLines(File file, String encoding, Collection lines, String lineEnding) {
-        try {
-            FileUtils.writeLines(file, encoding, lines, lineEnding);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void writeLines(File file, Collection lines, String lineEnding) {
-        try {
-            FileUtils.writeLines(file, lines, lineEnding);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
     public static void forceDelete(File file) {
         try {
             FileUtils.forceDelete(file);
@@ -428,58 +174,6 @@ public class GFileUtils {
         }
     }
 
-    public static void forceDeleteOnExit(File file) {
-        try {
-            FileUtils.forceDeleteOnExit(file);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void forceMkdir(File directory) {
-        try {
-            FileUtils.forceMkdir(directory);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static long sizeOfDirectory(File directory) {
-        return FileUtils.sizeOfDirectory(directory);
-    }
-
-    public static boolean isFileNewer(File file, File reference) {
-        return FileUtils.isFileNewer(file, reference);
-    }
-
-    public static boolean isFileNewer(File file, Date date) {
-        return FileUtils.isFileNewer(file, date);
-    }
-
-    public static boolean isFileNewer(File file, long timeMillis) {
-        return FileUtils.isFileNewer(file, timeMillis);
-    }
-
-    public static boolean isFileOlder(File file, File reference) {
-        return FileUtils.isFileOlder(file, reference);
-    }
-
-    public static boolean isFileOlder(File file, Date date) {
-        return FileUtils.isFileOlder(file, date);
-    }
-
-    public static boolean isFileOlder(File file, long timeMillis) {
-        return FileUtils.isFileOlder(file, timeMillis);
-    }
-
-    public static long checksumCRC32(File file) {
-        try {
-            return FileUtils.checksumCRC32(file);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
     public static Checksum checksum(File file, Checksum checksum) {
         try {
             return FileUtils.checksum(file, checksum);
@@ -488,46 +182,6 @@ public class GFileUtils {
         }
     }
 
-    public static void moveDirectory(File srcDir, File destDir) {
-        try {
-            FileUtils.moveDirectory(srcDir, destDir);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) {
-        try {
-            FileUtils.moveDirectoryToDirectory(src, destDir, createDestDir);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void moveFile(File srcFile, File destFile) {
-        try {
-            FileUtils.moveFile(srcFile, destFile);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) {
-        try {
-            FileUtils.moveFileToDirectory(srcFile, destDir, createDestDir);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    public static void moveToDirectory(File src, File destDir, boolean createDestDir) {
-        try {
-            FileUtils.moveToDirectory(src, destDir, createDestDir);
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
     public static File canonicalise(File src) {
         try {
             return src.getCanonicalFile();
@@ -535,65 +189,6 @@ public class GFileUtils {
             throw new UncheckedIOException(e);
         }
     }
-
-    public static List<File> getSubDirectories(File directory) {
-        final List<File> subDirectories = new ArrayList<File>();
-
-        addSubDirectories(directory, subDirectories);
-
-        return subDirectories;
-    }
-
-    public static void addSubDirectories(final File directory, final Collection<File> subDirectories) {
-        final File[] subFiles = directory.listFiles();
-
-        if (subFiles != null && subFiles.length > 0) {
-            for (final File subFile : subFiles) {
-                if (subFile.isDirectory()) {
-                    subDirectories.add(subFile);
-                    addSubDirectories(subFile, subDirectories);
-                }
-                // ignore files
-            }
-        }
-    }
-
-    public static List<File> getSubFiles(File directory) {
-        final List<File> subFilesList = new ArrayList<File>();
-
-        final File[] subFiles = directory.listFiles();
-        if (subFiles != null && subFiles.length > 0) {
-            for (final File subFile : subFiles) {
-                if (subFile.isFile()) {
-                    subFilesList.add(subFile);
-                }
-            }
-        }
-
-        return subFilesList;
-    }
-
-    public static boolean createDirectoriesWhenNotExistent(File... directories) {
-        if (directories != null && directories.length > 0) {
-            boolean directoriesCreated = true;
-            int directoriesIndex = 0;
-
-            while (directoriesCreated && directoriesIndex < directories.length) {
-                final File currentDirectory = directories[directoriesIndex];
-
-                if (!currentDirectory.exists()) {
-                    directoriesCreated = currentDirectory.mkdirs();
-                }
-
-                directoriesIndex++;
-            }
-
-            return directoriesCreated;
-        } else {
-            return true;
-        }
-    }
-
     /**
      * Creates a directory and any unexisting parent directories. Throws an
      * UncheckedIOException if it fails to do so.
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
index 4a6d9d6..e064ae5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GUtil.java
@@ -33,6 +33,9 @@ import static java.util.Collections.emptyList;
  * @author Hans Dockter
  */
 public class GUtil {
+    private static final Pattern WORD_SEPARATOR = Pattern.compile("\\W+");
+    private static final Pattern UPPER_LOWER = Pattern.compile("(\\p{Upper}*)(\\p{Lower}*)");
+
     public static <T extends Collection> T flatten(Object[] elements, T addTo, boolean flattenMaps) {
         return flatten(asList(elements), addTo, flattenMaps);
     }
@@ -103,7 +106,7 @@ public class GUtil {
     }
 
     public static String join(Collection self, String separator) {
-        StringBuffer buffer = new StringBuffer();
+        StringBuilder buffer = new StringBuilder();
         boolean first = true;
 
         if (separator == null) {
@@ -261,7 +264,7 @@ public class GUtil {
             return null;
         }
         StringBuilder builder = new StringBuilder();
-        Matcher matcher = Pattern.compile("[^\\w]+").matcher(string);
+        Matcher matcher = WORD_SEPARATOR.matcher(string);
         int pos = 0;
         while (matcher.find()) {
             builder.append(StringUtils.capitalize(string.subSequence(pos, matcher.start()).toString()));
@@ -271,6 +274,17 @@ public class GUtil {
         return builder.toString();
     }
 
+    public static String toLowerCamelCase(CharSequence string) {
+        String camelCase = toCamelCase(string);
+        if (camelCase == null) {
+            return null;
+        }
+        if (camelCase.length() == 0) {
+            return "";
+        }
+        return ((Character) camelCase.charAt(0)).toString().toLowerCase() + camelCase.subSequence(1, camelCase.length());
+    }
+
     /**
      * Converts an arbitrary string to upper case identifier with words separated by _. Eg, camelCase -> CAMEL_CASE
      */
@@ -294,7 +308,7 @@ public class GUtil {
         }
         StringBuilder builder = new StringBuilder();
         int pos = 0;
-        Matcher matcher = Pattern.compile("(\\p{Upper}*)(\\p{Lower}*)").matcher(string);
+        Matcher matcher = UPPER_LOWER.matcher(string);
         while (pos < string.length()) {
             matcher.find(pos);
             if (matcher.end() == pos) {
@@ -327,6 +341,11 @@ public class GUtil {
 
     public static byte[] serialize(Object object) {
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        serialize(object, outputStream);
+        return outputStream.toByteArray();
+    }
+
+    public static void serialize(Object object, OutputStream outputStream) {
         try {
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
             objectOutputStream.writeObject(object);
@@ -334,7 +353,6 @@ public class GUtil {
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
-        return outputStream.toByteArray();
     }
 
     public static <T> Comparator<T> last(final Comparator<? super T> comparator, final T lastValue) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java b/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
index 0e12aa0..80350d6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/GradleVersion.java
@@ -20,22 +20,20 @@ import groovy.lang.GroovySystem;
 import org.apache.ivy.Ivy;
 import org.apache.tools.ant.Main;
 import org.gradle.api.GradleException;
-import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.UncheckedIOException;
 import org.gradle.internal.UncheckedException;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.os.OperatingSystem;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.net.URLConnection;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.Properties;
 import java.util.TimeZone;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -46,7 +44,7 @@ import java.util.regex.Pattern;
  */
 public class GradleVersion implements Comparable<GradleVersion> {
     public final static String URL = "http://www.gradle.org";
-    private final static Pattern VERSION_PATTERN = Pattern.compile("(\\d+(\\.\\d+)+)(-(\\p{Alpha}+)-(\\d+[a-z]?))?(-(\\d{14}[-+]\\d{4}))?");
+    private final static Pattern VERSION_PATTERN = Pattern.compile("(\\d+(\\.\\d+)+)(-(\\p{Alpha}+)-(\\d+[a-z]?))?(-(\\d{14}([-+]\\d{4})?))?");
 
     private final String version;
     private final String buildTime;
@@ -55,28 +53,34 @@ public class GradleVersion implements Comparable<GradleVersion> {
     private final Stage stage;
     private static final GradleVersion CURRENT;
 
-    public static final String RESOURCE_NAME = "/org/gradle/releases.xml";
+    public static final String RESOURCE_NAME = "/org/gradle/build-receipt.properties";
 
+    // TODO - get rid of this static initialiser nonsense
     static {
         URL resource = GradleVersion.class.getResource(RESOURCE_NAME);
-        Document document;
+
+        InputStream inputStream = null;
         try {
-            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-            DocumentBuilder builder = factory.newDocumentBuilder();
-            InputStream inputStream = resource.openStream();
-            try {
-                document = builder.parse(inputStream);
-            } finally {
-                inputStream.close();
-            }
-            NodeList currentElements = document.getDocumentElement().getElementsByTagName("current");
-            if (currentElements.getLength() != 1) {
-                throw new GradleException(String.format("Expected to find 1 <current> element, found %s.", currentElements.getLength()));
-            }
-            Element currentRelease = (Element) currentElements.item(0);
-            CURRENT = new GradleVersion(currentRelease.getAttribute("version"), new SimpleDateFormat("yyyyMMddHHmmssZ").parse(currentRelease.getAttribute("build-time")));
+            URLConnection connection = resource.openConnection();
+            inputStream = connection.getInputStream();
+            Properties properties = new Properties();
+            properties.load(inputStream);
+
+            String version = properties.get("versionNumber").toString();
+            String buildTimestamp = properties.get("buildTimestamp").toString();
+            Date buildTime = new SimpleDateFormat("yyyyMMddHHmmssZ").parse(buildTimestamp);
+
+            CURRENT = new GradleVersion(version, buildTime);
         } catch (Exception e) {
             throw new GradleException(String.format("Could not load version details from resource '%s'.", resource), e);
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
         }
     }
 
@@ -93,8 +97,13 @@ public class GradleVersion implements Comparable<GradleVersion> {
         this.buildTime = buildTime == null ? null : formatBuildTime(buildTime);
         Matcher matcher = VERSION_PATTERN.matcher(version);
         if (!matcher.matches()) {
-            throw new InvalidUserDataException(String.format("Unexpected Gradle version '%s'.", version));
+            // Unrecognized version
+            versionPart = null;
+            snapshot = null;
+            stage = null;
+            return;
         }
+
         versionPart = matcher.group(1);
 
         if (matcher.group(3) != null) {
@@ -114,7 +123,13 @@ public class GradleVersion implements Comparable<GradleVersion> {
 
         if (matcher.group(7) != null) {
             try {
-                snapshot = new SimpleDateFormat("yyyyMMddHHmmssZ").parse(matcher.group(7)).getTime();
+                if (matcher.group(8) != null) {
+                    snapshot = new SimpleDateFormat("yyyyMMddHHmmssZ").parse(matcher.group(7)).getTime();
+                } else {
+                    SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
+                    format.setTimeZone(TimeZone.getTimeZone("UTC"));
+                    snapshot = format.parse(matcher.group(7)).getTime();
+                }
             } catch (ParseException e) {
                 throw UncheckedException.throwAsUncheckedException(e);
             }
@@ -143,15 +158,31 @@ public class GradleVersion implements Comparable<GradleVersion> {
     }
 
     public boolean isSnapshot() {
-        return snapshot != null;
+        return versionPart == null || snapshot != null;
+    }
+
+    private boolean isNonSymbolicNumber() {
+        return versionPart.equals("0.0");
     }
 
     public int compareTo(GradleVersion gradleVersion) {
+        assertCanQueryParts();
+        gradleVersion.assertCanQueryParts();
+
+        if (isNonSymbolicNumber() && !gradleVersion.isNonSymbolicNumber()) {
+            return 1;
+        } else if (!isNonSymbolicNumber() && gradleVersion.isNonSymbolicNumber()) {
+            return -1;
+        }
+
         String[] majorVersionParts = versionPart.split("\\.");
         String[] otherMajorVersionParts = gradleVersion.versionPart.split("\\.");
+
+
         for (int i = 0; i < majorVersionParts.length && i < otherMajorVersionParts.length; i++) {
             int part = Integer.parseInt(majorVersionParts[i]);
             int otherPart = Integer.parseInt(otherMajorVersionParts[i]);
+
             if (part > otherPart) {
                 return 1;
             }
@@ -192,6 +223,12 @@ public class GradleVersion implements Comparable<GradleVersion> {
         return 0;
     }
 
+    private void assertCanQueryParts() {
+        if (versionPart == null) {
+            throw new IllegalArgumentException(String.format("Cannot compare unrecognized Gradle version '%s'.", version));
+        }
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o == this) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/IdGenerator.java b/subprojects/core/src/main/groovy/org/gradle/util/IdGenerator.java
deleted file mode 100644
index 9c5add0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/IdGenerator.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.util;
-
-/**
- * Generates a sequence of unique ids of type T. Implementations must be thread-safe.
- */
-public interface IdGenerator<T> {
-    /**
-     * Generates a new id. Values must be serializable.
-     *
-     * @return The id. Must not return null. Must not return a given value more than once.
-     */
-    T generateId();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java b/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java
index fb5d7a7..bbe0d9e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/JavaMethod.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2010 the original author or authors.
+ * Copyright 2012 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.
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/JavaReflectionUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/JavaReflectionUtil.java
deleted file mode 100644
index d4f5178..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/JavaReflectionUtil.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.util;
-
-import org.apache.commons.lang.reflect.FieldUtils;
-import org.gradle.internal.UncheckedException;
-
-public class JavaReflectionUtil {
-    // TODO: use setter instead of field
-    public static void setProperty(Object target, String property, Object value) {
-        try {
-            FieldUtils.writeDeclaredField(target, property, value, true);
-        } catch (IllegalAccessException e) {
-            throw new UncheckedException(e);
-        }
-    }
-
-    // TODO: use getter instead of field
-    public static Object getProperty(Object target, String property) {
-        try {
-            return FieldUtils.readDeclaredField(target, property, true);
-        } catch (IllegalAccessException e) {
-            throw new UncheckedException(e);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java b/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
index d8f2d19..a9f979d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/Jvm.java
@@ -48,23 +48,23 @@ public class Jvm implements JavaInfo {
     }
 
     public boolean isJava5() {
-        return jvm.isJava5();
+        return jvm.getJavaVersion().isJava5();
     }
 
     public boolean isJava6() {
-        return jvm.isJava6();
+        return jvm.getJavaVersion().isJava6();
     }
 
     public boolean isJava7() {
-        return jvm.isJava7();
+        return jvm.getJavaVersion().isJava7();
     }
 
     public boolean isJava5Compatible() {
-        return jvm.isJava5Compatible();
+        return jvm.getJavaVersion().isJava5Compatible();
     }
 
     public boolean isJava6Compatible() {
-        return jvm.isJava6Compatible();
+        return jvm.getJavaVersion().isJava6Compatible();
     }
 
     public File getJavaHome() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/LongIdGenerator.java b/subprojects/core/src/main/groovy/org/gradle/util/LongIdGenerator.java
deleted file mode 100644
index 155bae2..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/LongIdGenerator.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.util;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-public class LongIdGenerator implements IdGenerator<Long> {
-    private final AtomicLong nextId = new AtomicLong(1);
-
-    public Long generateId() {
-        return nextId.getAndIncrement();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.java b/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.java
index 364a1eb..c8a50ce 100755
--- a/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/MutableURLClassLoader.java
@@ -16,6 +16,8 @@
 
 package org.gradle.util;
 
+import org.gradle.internal.classpath.ClassPath;
+
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Collection;
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/RandomLongIdGenerator.java b/subprojects/core/src/main/groovy/org/gradle/util/RandomLongIdGenerator.java
deleted file mode 100644
index d79692a..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/RandomLongIdGenerator.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.util;
-
-import java.util.Random;
-
-public class RandomLongIdGenerator implements IdGenerator<Long> {
-    private final Random random = new Random();
-
-    public Long generateId() {
-        return random.nextLong();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy b/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
index e27684d..80a1e05 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
@@ -43,23 +43,4 @@ class ReflectionUtil {
             return false
         }
     }
-
-    static Class<?> getWrapperTypeForPrimitiveType(Class<?> type) {
-        if (type == Boolean.TYPE) {
-            return Boolean.class;
-        } else if (type == Long.TYPE) {
-            return Long.class;
-        } else if (type == Integer.TYPE) {
-            return Integer.class;
-        } else if (type == Short.TYPE) {
-            return Short.class;
-        } else if (type == Byte.TYPE) {
-            return Byte.class;
-        } else if (type == Float.TYPE) {
-            return Float.class;
-        } else if (type == Double.TYPE) {
-            return Double.class;
-        }
-        throw new IllegalArgumentException(String.format("Don't know how wrapper type for primitive type %s.", type));
-    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ServiceLocator.java b/subprojects/core/src/main/groovy/org/gradle/util/ServiceLocator.java
deleted file mode 100644
index 00f847c..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/ServiceLocator.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.util;
-
-import org.gradle.api.internal.DirectInstantiator;
-import org.gradle.internal.Factory;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.internal.service.UnknownServiceException;
-import org.gradle.api.internal.Instantiator;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Uses the Jar service resource specification to locate service implementations.
- */
-public class ServiceLocator implements ServiceRegistry {
-    private final ClassLoader classLoader;
-    private final Map<Class<?>, Object> implementations = new ConcurrentHashMap<Class<?>, Object>();
-
-    public ServiceLocator(ClassLoader classLoader) {
-        this.classLoader = classLoader;
-    }
-
-    public <T> T get(Class<T> serviceType) throws UnknownServiceException {
-        synchronized (implementations) {
-            T implementation = serviceType.cast(implementations.get(serviceType));
-            if (implementation == null) {
-                implementation = getFactory(serviceType).create();
-                implementations.put(serviceType, implementation);
-            }
-            return implementation;
-        }
-    }
-
-    public <T> ServiceFactory<T> getFactory(final Class<T> serviceType) throws UnknownServiceException {
-        ServiceFactory<T> factory = findFactory(serviceType);
-        if (factory == null) {
-            throw new UnknownServiceException(serviceType, String.format("Could not find meta-data resource 'META-INF/services/%s' for service '%s'.", serviceType.getName(), serviceType.getName()));
-        }
-        return factory;
-    }
-
-    /**
-     * Locates a factory for a given service. Returns null when no service implementation is available.
-     */
-    public <T> ServiceFactory<T> findFactory(Class<T> serviceType) {
-        Class<? extends T> implementationClass = findServiceImplementationClass(serviceType);
-        if (implementationClass == null) {
-            return null;
-        }
-        return new ServiceFactory<T>(serviceType, implementationClass);
-    }
-
-    public <T> T newInstance(Class<T> type) throws UnknownServiceException {
-        return getFactory(type).create();
-    }
-
-    <T> Class<? extends T> findServiceImplementationClass(Class<T> serviceType) {
-        String implementationClassName;
-        try {
-            implementationClassName = findServiceImplementationClassName(serviceType);
-        } catch (Exception e) {
-            throw new RuntimeException(String.format("Could not determine implementation class for service '%s'.", serviceType.getName()), e);
-        }
-        if (implementationClassName == null) {
-            return null;
-        }
-        try {
-            Class<?> implClass = classLoader.loadClass(implementationClassName);
-            if (!serviceType.isAssignableFrom(implClass)) {
-                throw new RuntimeException(String.format("Implementation class '%s' is not assignable to service class '%s'.", implementationClassName, serviceType.getName()));
-            }
-            return implClass.asSubclass(serviceType);
-        } catch (Throwable t) {
-            throw new RuntimeException(String.format("Could not load implementation class '%s' for service '%s'.", implementationClassName, serviceType.getName()), t);
-        }
-    }
-
-    private String findServiceImplementationClassName(Class<?> serviceType) throws IOException {
-        String resourceName = "META-INF/services/" + serviceType.getName();
-        URL resource = classLoader.getResource(resourceName);
-        if (resource == null) {
-            return null;
-        }
-
-        InputStream inputStream = resource.openStream();
-        try {
-            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-            String line;
-            while ((line = reader.readLine()) != null) {
-                line = line.replaceAll("#.*", "").trim();
-                if (line.length() > 0) {
-                    return line;
-                }
-            }
-        } finally {
-            inputStream.close();
-        }
-        throw new RuntimeException(String.format("No implementation class for service '%s' specified in resource '%s'.", serviceType.getName(), resource));
-    }
-
-    public static class ServiceFactory<T> implements Factory<T> {
-        private final Class<T> serviceType;
-        private final Class<? extends T> implementationClass;
-
-        public ServiceFactory(Class<T> serviceType, Class<? extends T> implementationClass) {
-            this.serviceType = serviceType;
-            this.implementationClass = implementationClass;
-        }
-
-        public Class<? extends T> getImplementationClass() {
-            return implementationClass;
-        }
-
-        public T create() {
-            return newInstance();
-        }
-
-        public T newInstance(Object... params) {
-            Instantiator instantiator = new DirectInstantiator();
-            try {
-                return instantiator.newInstance(implementationClass, params);
-            } catch (Throwable t) {
-                throw new RuntimeException(String.format("Could not create an implementation of service '%s'.", serviceType.getName()), t);
-            }
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
index 5c6cc32..14e4abf 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/TextUtil.java
@@ -19,7 +19,11 @@ package org.gradle.util;
 import org.apache.commons.lang.StringEscapeUtils;
 import org.gradle.internal.SystemProperties;
 
+import java.util.regex.Pattern;
+
 public class TextUtil {
+    private static final Pattern WHITESPACE = Pattern.compile("\\s*");
+
     /**
      * Returns the line separator for Windows.
      */
@@ -75,4 +79,26 @@ public class TextUtil {
         }
         return false;
     }
+
+    /**
+     * Indents every line of {@code text} by {@code indent}. Empty lines
+     * and lines that only contain whitespace are not indented.
+     */
+    public static String indent(String text, String indent) {
+        StringBuilder builder = new StringBuilder();
+        String[] lines = text.split("\n");
+
+        for (int i = 0; i < lines.length; i++) {
+            String line = lines[i];
+            if (!WHITESPACE.matcher(line).matches()) {
+                builder.append(indent);
+            }
+            builder.append(line);
+            if (i < lines.length - 1) {
+                builder.append('\n');
+            }
+        }
+
+        return builder.toString();
+    }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/TimeProvider.java b/subprojects/core/src/main/groovy/org/gradle/util/TimeProvider.java
deleted file mode 100644
index cde7956..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/TimeProvider.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.util;
-
-public interface TimeProvider {
-
-    long getCurrentTime();
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/ToStringTransformer.java b/subprojects/core/src/main/groovy/org/gradle/util/ToStringTransformer.java
new file mode 100644
index 0000000..9bf5220
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/ToStringTransformer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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 org.gradle.util;
+
+import org.gradle.api.Transformer;
+
+public class ToStringTransformer implements Transformer<String, Object> {
+
+    public String transform(Object original) {
+        return original.toString();
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/TrueTimeProvider.java b/subprojects/core/src/main/groovy/org/gradle/util/TrueTimeProvider.java
deleted file mode 100644
index a4868f9..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/TrueTimeProvider.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.util;
-
-public class TrueTimeProvider implements TimeProvider {
-
-    public long getCurrentTime() {
-        return System.currentTimeMillis();
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/UUIDGenerator.java b/subprojects/core/src/main/groovy/org/gradle/util/UUIDGenerator.java
deleted file mode 100644
index 6c6f7d6..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/util/UUIDGenerator.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.util;
-
-import java.util.UUID;
-
-public class UUIDGenerator implements IdGenerator<UUID> {
-    public UUID generateId() {
-        return UUID.randomUUID();
-    }
-}
diff --git a/subprojects/core/src/releases.xml b/subprojects/core/src/releases.xml
deleted file mode 100644
index 62ae528..0000000
--- a/subprojects/core/src/releases.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<releases>
-  <next version="1.0"/>
-  <current version="${version}" build-time="${buildTime}" type="${releaseType}"/>
-  <release version="1.0-rc-3" build-time="20120430015152+0200"/>
-  <release version="1.0-rc-2" build-time="20120425015237+0200"/>
-  <release version="1.0-rc-1" build-time="20120411121324+0100"/>
-  <release version="1.0-milestone-9" build-time="20120313171009+0100"/>
-  <release version="1.0-milestone-8a" build-time="20120220185357+0100"/>
-  <release version="1.0-milestone-8" build-time="20120214022451+0100"/>
-  <release version="1.0-milestone-7" build-time="20120105102443+0000"/>
-  <release version="1.0-milestone-6" build-time="20111117065412+0100"/>
-  <release version="1.0-milestone-5" build-time="20111025055608+0200"/>
-  <release version="1.0-milestone-4" build-time="20110728103822+0200" status="broken"/>
-  <release version="1.0-milestone-3" build-time="20110425174011+1000"/>
-  <release version="1.0-milestone-2" build-time="20110407163255+1000"/>
-  <release version="1.0-milestone-1" build-time="20110227141320+1100"/>
-  <release version="0.9.2" build-time="20110123133421+1100"/>
-  <release version="0.9.1" build-time="20110102114057+1100"/>
-  <release version="0.9" build-time="20101219125006+1100"/>
-  <release version="0.9-rc-3" build-time="20101120131750+1100"/>
-  <release version="0.9-rc-2" build-time="20101027082405+1100"/>
-  <release version="0.9-rc-1" build-time="20100804080433+1100"/>
-  <release version="0.8" build-time="20090928140159+0200"/>
-  <release version="0.7" build-time="20090720085013+0200"/>
-</releases>
diff --git a/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
index b426ecb..7a55941 100644
--- a/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/BuildExceptionReporterTest.groovy
@@ -18,16 +18,16 @@ package org.gradle
 import org.gradle.api.GradleException
 import org.gradle.api.internal.LocationAwareException
 import org.gradle.api.logging.LogLevel
+import org.gradle.api.internal.AbstractMultiCauseException
 import org.gradle.execution.TaskSelectionException
 import org.gradle.initialization.BuildClientMetaData
 import org.gradle.logging.LoggingConfiguration
 import org.gradle.logging.ShowStacktrace
 import org.gradle.logging.StyledTextOutputFactory
-import org.gradle.logging.internal.TestStyledTextOutput
+import org.gradle.logging.TestStyledTextOutput
 import org.gradle.util.TreeVisitor
+
 import spock.lang.Specification
-import org.gradle.api.internal.MultiCauseException
-import org.gradle.api.internal.AbstractMultiCauseException
 
 class BuildExceptionReporterTest extends Specification {
     final TestStyledTextOutput output = new TestStyledTextOutput()
diff --git a/subprojects/core/src/test/groovy/org/gradle/BuildResultLoggerTest.java b/subprojects/core/src/test/groovy/org/gradle/BuildResultLoggerTest.java
index 1a218d7..f8ac943 100644
--- a/subprojects/core/src/test/groovy/org/gradle/BuildResultLoggerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/BuildResultLoggerTest.java
@@ -17,7 +17,7 @@ package org.gradle;
 
 import org.gradle.api.logging.LogLevel;
 import org.gradle.logging.StyledTextOutputFactory;
-import org.gradle.logging.internal.TestStyledTextOutput;
+import org.gradle.logging.TestStyledTextOutput;
 import org.gradle.util.Clock;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.jmock.Expectations;
diff --git a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
index ee8ee06..93e02d7 100644
--- a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
@@ -50,7 +50,6 @@ class StartParameterTest {
         testObj.logLevel = LogLevel.WARN
         testObj.colorOutput = false
         testObj.continueOnFailure = true
-        testObj.refreshOptions = RefreshOptions.fromCommandLineOptions(['dependencies'])
         testObj.rerunTasks = true;
         testObj.refreshDependencies = true;
         testObj.recompileScripts = true;
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/JavaVersionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/JavaVersionTest.java
deleted file mode 100644
index 7083219..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/JavaVersionTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api;
-
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.nullValue;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-public class JavaVersionTest {
-    @Test
-    public void toStringReturnsVersion() {
-        assertThat(JavaVersion.VERSION_1_3.toString(), equalTo("1.3"));
-        assertThat(JavaVersion.VERSION_1_4.toString(), equalTo("1.4"));
-        assertThat(JavaVersion.VERSION_1_5.toString(), equalTo("1.5"));
-        assertThat(JavaVersion.VERSION_1_6.toString(), equalTo("1.6"));
-        assertThat(JavaVersion.VERSION_1_7.toString(), equalTo("1.7"));
-    }
-
-    @Test
-    public void convertsStringToVersion() {
-        assertThat(JavaVersion.toVersion("1.3"), equalTo(JavaVersion.VERSION_1_3));
-        assertThat(JavaVersion.toVersion("1.5"), equalTo(JavaVersion.VERSION_1_5));
-        assertThat(JavaVersion.toVersion("1.5.4"), equalTo(JavaVersion.VERSION_1_5));
-        assertThat(JavaVersion.toVersion("1.5_4"), equalTo(JavaVersion.VERSION_1_5));
-        assertThat(JavaVersion.toVersion("1.5.0.4_b109"), equalTo(JavaVersion.VERSION_1_5));
-
-        assertThat(JavaVersion.toVersion("5"), equalTo(JavaVersion.VERSION_1_5));
-        assertThat(JavaVersion.toVersion("6"), equalTo(JavaVersion.VERSION_1_6));
-        assertThat(JavaVersion.toVersion("7"), equalTo(JavaVersion.VERSION_1_7));
-    }
-
-    @Test
-    public void failsToConvertStringToVersionForUnknownVersion() {
-        conversionFails("1");
-        conversionFails("2");
-
-        conversionFails("8");
-
-        conversionFails("a");
-        conversionFails("");
-        conversionFails("  ");
-
-        conversionFails("1.54");
-        conversionFails("1.10");
-        conversionFails("2.0");
-        conversionFails("1_4");
-    }
-
-    @Test
-    public void convertsVersionToVersion() {
-        assertThat(JavaVersion.toVersion(JavaVersion.VERSION_1_4), equalTo(JavaVersion.VERSION_1_4));
-    }
-    
-    @Test
-    public void convertsNumberToVersion() {
-        assertThat(JavaVersion.toVersion(1.3), equalTo(JavaVersion.VERSION_1_3));
-        assertThat(JavaVersion.toVersion(1.5), equalTo(JavaVersion.VERSION_1_5));
-        assertThat(JavaVersion.toVersion(5), equalTo(JavaVersion.VERSION_1_5));
-        assertThat(JavaVersion.toVersion(6), equalTo(JavaVersion.VERSION_1_6));
-        assertThat(JavaVersion.toVersion(7), equalTo(JavaVersion.VERSION_1_7));
-        assertThat(JavaVersion.toVersion(1.7), equalTo(JavaVersion.VERSION_1_7));
-    }
-    
-    @Test
-    public void failsToConvertNumberToVersionForUnknownVersion() {
-        conversionFails(1);
-        conversionFails(2);
-        conversionFails(8);
-        conversionFails(1.21);
-        conversionFails(2.0);
-        conversionFails(4.2);
-    }
-    
-    @Test
-    public void currentReturnsJvmVersion() {
-        assertThat(JavaVersion.current(), equalTo(JavaVersion.toVersion(System.getProperty("java.version"))));
-    }
-
-    @Test
-    public void convertsNullToNull() {
-        assertThat(JavaVersion.toVersion(null), nullValue());
-    }
-
-    private void conversionFails(Object value) {
-        try {
-            JavaVersion.toVersion(value);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("Could not determine java version from '" + value + "'."));
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
deleted file mode 100755
index b9a73ba..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal;
-
-import groovy.lang.Closure;
-import groovy.lang.GroovyObject;
-import org.gradle.api.Action;
-import org.gradle.api.GradleException;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.plugins.DslObject;
-import org.gradle.api.plugins.Convention;
-import org.gradle.api.plugins.ExtensionAware;
-import org.gradle.api.plugins.ExtensionContainer;
-import org.junit.Before;
-import org.junit.Test;
-import spock.lang.Issue;
-
-import java.util.*;
-import java.util.concurrent.Callable;
-
-import static org.gradle.util.HelperUtil.TEST_CLOSURE;
-import static org.gradle.util.HelperUtil.call;
-import static org.gradle.util.Matchers.isEmpty;
-import static org.gradle.util.WrapUtil.toList;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-import static org.gradle.api.internal.AbstractClassGeneratorTestGroovy.*;
-
-public abstract class AbstractClassGeneratorTest {
-    private AbstractClassGenerator generator;
-
-    @Before
-    public void setUp() {
-        generator = createGenerator();
-    }
-
-    protected abstract AbstractClassGenerator createGenerator();
-
-    @Test
-    public void mixesInConventionAwareInterface() throws Exception {
-        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
-        assertTrue(IConventionAware.class.isAssignableFrom(generatedClass));
-
-        Bean bean = generatedClass.newInstance();
-
-        IConventionAware conventionAware = (IConventionAware) bean;
-        assertThat(conventionAware.getConventionMapping(), instanceOf(ConventionAwareHelper.class));
-        conventionAware.getConventionMapping().map("prop", TEST_CLOSURE);
-        ConventionMapping mapping = new ConventionAwareBean();
-    }
-
-    @Test
-    public void mixesInDynamicObjectAwareInterface() throws Exception {
-        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
-        assertTrue(DynamicObjectAware.class.isAssignableFrom(generatedClass));
-        Bean bean = generatedClass.newInstance();
-        DynamicObjectAware dynamicBean = (DynamicObjectAware) bean;
-
-        dynamicBean.getAsDynamicObject().setProperty("prop", "value");
-        assertThat(bean.getProp(), equalTo("value"));
-        assertThat(bean.doStuff("some value"), equalTo("{some value}"));
-    }
-
-    @Test
-    public void mixesInExtensionAwareInterface() throws Exception {
-        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
-        assertTrue(ExtensionAware.class.isAssignableFrom(generatedClass));
-        Bean bean = generatedClass.newInstance();
-        ExtensionAware dynamicBean = (ExtensionAware) bean;
-
-        assertThat(dynamicBean.getExtensions(), notNullValue());
-    }
-
-    @Test
-    public void mixesInGroovyObjectInterface() throws Exception {
-        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
-        assertTrue(GroovyObject.class.isAssignableFrom(generatedClass));
-        Bean bean = generatedClass.newInstance();
-        GroovyObject groovyObject = (GroovyObject) bean;
-        assertThat(groovyObject.getMetaClass(), notNullValue());
-
-        groovyObject.setProperty("prop", "value");
-        assertThat(bean.getProp(), equalTo("value"));
-        assertThat(groovyObject.getProperty("prop"), equalTo((Object) "value"));
-        assertThat(groovyObject.invokeMethod("doStuff", new Object[]{"some value"}), equalTo((Object) "{some value}"));
-    }
-
-    @Test
-    public void cachesGeneratedSubclass() {
-        assertSame(generator.generate(Bean.class), generator.generate(Bean.class));
-    }
-
-    @Test
-    public void doesNotDecorateAlreadyDecoratedClass() {
-        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
-        assertSame(generatedClass, generator.generate(generatedClass));
-    }
-
-    @Test
-    public void overridesPublicConstructors() throws Exception {
-        Class<? extends Bean> generatedClass = generator.generate(BeanWithConstructor.class);
-        Bean bean = generatedClass.getConstructor(String.class).newInstance("value");
-        assertThat(bean.getProp(), equalTo("value"));
-
-        bean = generatedClass.getConstructor().newInstance();
-        assertThat(bean.getProp(), equalTo("default value"));
-    }
-
-    @Test
-    public void canConstructInstance() throws Exception {
-        Bean bean = generator.newInstance(BeanWithConstructor.class, "value");
-        assertThat(bean.getClass(), sameInstance((Object) generator.generate(BeanWithConstructor.class)));
-        assertThat(bean.getProp(), equalTo("value"));
-
-        bean = generator.newInstance(BeanWithConstructor.class);
-        assertThat(bean.getProp(), equalTo("default value"));
-
-        bean = generator.newInstance(BeanWithConstructor.class, 127);
-        assertThat(bean.getProp(), equalTo("127"));
-    }
-
-    @Test
-    public void reportsConstructionFailure() {
-        try {
-            generator.newInstance(UnconstructableBean.class);
-            fail();
-        } catch (UnsupportedOperationException e) {
-            assertThat(e, sameInstance(UnconstructableBean.failure));
-        }
-
-        try {
-            generator.newInstance(Bean.class, "arg1", 2);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            generator.newInstance(AbstractBean.class);
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), equalTo("Cannot create a proxy class for abstract class 'AbstractBean'."));
-        }
-
-        try {
-            generator.newInstance(PrivateBean.class);
-            fail();
-        } catch (GradleException e) {
-            assertThat(e.getMessage(), equalTo("Cannot create a proxy class for private class 'PrivateBean'."));
-        }
-    }
-
-    @Test
-    public void appliesConventionMappingToEachGetter() throws Exception {
-        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
-        assertTrue(IConventionAware.class.isAssignableFrom(generatedClass));
-        Bean bean = generatedClass.newInstance();
-        IConventionAware conventionAware = (IConventionAware) bean;
-
-        assertThat(bean.getProp(), nullValue());
-
-        conventionAware.getConventionMapping().map("prop", new Callable<String>() {
-            public String call() {
-                return "conventionValue";
-            }
-        });
-
-        assertThat(bean.getProp(), equalTo("conventionValue"));
-
-        bean.setProp("value");
-        assertThat(bean.getProp(), equalTo("value"));
-
-        bean.setProp(null);
-        assertThat(bean.getProp(), nullValue());
-    }
-
-    @Test
-    @Issue("GRADLE-2163")
-    public void appliesConventionMappingToGroovyBoolean() throws Exception {
-        BeanWithGroovyBoolean bean = generator.generate(BeanWithGroovyBoolean.class).newInstance();
-
-        assertTrue(bean instanceof IConventionAware);
-        assertThat(bean.getSmallB(), equalTo(false));
-        assertThat(bean.getBigB(), nullValue());
-
-        IConventionAware conventionAware = (IConventionAware) bean;
-
-        conventionAware.getConventionMapping().map("smallB", new Callable<Object>() {
-            public Object call() throws Exception {
-                return true;
-            }
-        });
-
-        assertThat(bean.isSmallB(), equalTo(true));
-        assertThat(bean.getSmallB(), equalTo(true));
-
-        bean.setSmallB(false);
-        assertThat(bean.isSmallB(), equalTo(false));
-        assertThat(bean.getSmallB(), equalTo(false));
-
-        conventionAware.getConventionMapping().map("bigB", new Callable<Object>() {
-            public Object call() throws Exception {
-                return Boolean.TRUE;
-            }
-        });
-
-        assertThat(bean.getBigB(), equalTo(Boolean.TRUE));
-        bean.setBigB(Boolean.FALSE);
-        assertThat(bean.getBigB(), equalTo(Boolean.FALSE));
-    }
-
-    @Test
-    public void appliesConventionMappingToCollectionGetter() throws Exception {
-        Class<? extends CollectionBean> generatedClass = generator.generate(CollectionBean.class);
-        CollectionBean bean = generatedClass.newInstance();
-        IConventionAware conventionAware = (IConventionAware) bean;
-        final List<String> conventionValue = toList("value");
-
-        assertThat(bean.getProp(), isEmpty());
-
-        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
-            public Object call() {
-                return conventionValue;
-            }
-        });
-
-        assertThat(bean.getProp(), sameInstance(conventionValue));
-
-        bean.setProp(toList("other"));
-        assertThat(bean.getProp(), equalTo(toList("other")));
-
-        bean.setProp(Collections.<String>emptyList());
-        assertThat(bean.getProp(), equalTo(Collections.<String>emptyList()));
-
-        bean.setProp(null);
-        assertThat(bean.getProp(), nullValue());
-    }
-
-    @Test
-    public void handlesVariousPropertyTypes() throws Exception {
-        BeanWithVariousPropertyTypes bean = generator.generate(BeanWithVariousPropertyTypes.class).newInstance();
-
-        assertThat(bean.getArrayProperty(), notNullValue());
-        assertThat(bean.getBooleanProperty(), equalTo(false));
-        assertThat(bean.getLongProperty(), equalTo(12L));
-        assertThat(bean.setReturnValueProperty("p"), sameInstance(bean));
-
-        IConventionAware conventionAware = (IConventionAware) bean;
-        conventionAware.getConventionMapping().map("booleanProperty", new Callable<Object>() {
-            public Object call() throws Exception {
-                return true;
-            }
-        });
-
-        assertThat(bean.getBooleanProperty(), equalTo(true));
-
-        bean.setBooleanProperty(false);
-        assertThat(bean.getBooleanProperty(), equalTo(false));
-    }
-
-    @Test
-    public void doesNotOverrideMethodsFromConventionAwareInterface() throws Exception {
-        Class<? extends ConventionAwareBean> generatedClass = generator.generate(ConventionAwareBean.class);
-        assertTrue(IConventionAware.class.isAssignableFrom(generatedClass));
-        ConventionAwareBean bean = generatedClass.newInstance();
-        assertSame(bean, bean.getConventionMapping());
-
-        bean.setProp("value");
-        assertEquals("[value]", bean.getProp());
-    }
-
-    @Test
-    public void doesNotOverrideMethodsFromSuperclassesMarkedWithAnnotation() throws Exception {
-        BeanSubClass bean = generator.generate(BeanSubClass.class).newInstance();
-        IConventionAware conventionAware = (IConventionAware) bean;
-        conventionAware.getConventionMapping().map("property", new Callable<Object>() {
-            public Object call() throws Exception {
-                throw new UnsupportedOperationException();
-            }
-        });
-        conventionAware.getConventionMapping().map("interfaceProperty", new Callable<Object>() {
-            public Object call() throws Exception {
-                throw new UnsupportedOperationException();
-            }
-        });
-        conventionAware.getConventionMapping().map("overriddenProperty", new Callable<Object>() {
-            public Object call() throws Exception {
-                return "conventionValue";
-            }
-        });
-        conventionAware.getConventionMapping().map("otherProperty", new Callable<Object>() {
-            public Object call() throws Exception {
-                return "conventionValue";
-            }
-        });
-        assertEquals(null, bean.getProperty());
-        assertEquals(null, bean.getInterfaceProperty());
-        assertEquals("conventionValue", bean.getOverriddenProperty());
-        assertEquals("conventionValue", bean.getOtherProperty());
-    }
-
-    @Test
-    public void doesNotMixInConventionMappingToClassWithAnnotation() throws Exception {
-        NoMappingBean bean = generator.generate(NoMappingBean.class).newInstance();
-        assertFalse(bean instanceof IConventionAware);
-        assertNull(bean.getInterfaceProperty());
-
-        // Check dynamic object behaviour still works
-        assertTrue(bean instanceof DynamicObjectAware);
-    }
-
-    @Test
-    public void doesNotOverrideMethodsFromDynamicObjectAwareInterface() throws Exception {
-        DynamicObjectAwareBean bean = generator.generate(DynamicObjectAwareBean.class).newInstance();
-        assertThat(bean.getConvention(), sameInstance(bean.conv));
-        assertThat(bean.getAsDynamicObject(), sameInstance(bean.conv.getExtensionsAsDynamicObject()));
-    }
-
-    @Test
-    public void doesNotMixInDynamicObjectToClassWithAnnotation() throws Exception {
-        Class<? extends NoDynamicBean> generatedType = generator.generate(NoDynamicBean.class);
-        assertFalse(DynamicObjectAware.class.isAssignableFrom(generatedType));
-
-        // Check convention mapping still works
-        assertTrue(IConventionAware.class.isAssignableFrom(generatedType));
-        NoDynamicBean bean = generatedType.newInstance();
-
-        // Check MOP methods not overridden
-        bean.setProp("value");
-        assertThat(call("{ it.prop }", bean), equalTo((Object) "value"));
-        assertThat(call("{ it.dynamicProp }", bean), equalTo((Object) "[dynamicProp]"));
-    }
-
-    @Test
-    public void canAddDynamicPropertiesAndMethodsToJavaObject() throws Exception {
-        Bean bean = generator.generate(Bean.class).newInstance();
-        DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
-        ConventionObject conventionObject = new ConventionObject();
-        new DslObject(dynamicObjectAware).getConvention().getPlugins().put("plugin", conventionObject);
-
-        call("{ it.conventionProperty = 'value' }", bean);
-        assertThat(conventionObject.getConventionProperty(), equalTo("value"));
-        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
-        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
-        assertThat(call("{ it.conventionMethod('value') }", bean), equalTo((Object) "[value]"));
-        assertThat(call("{ it.invokeMethod('conventionMethod', 'value') }", bean), equalTo((Object) "[value]"));
-    }
-
-    @Test
-    public void canAddDynamicPropertiesAndMethodsToGroovyObject() throws Exception {
-        TestDecoratedGroovyBean bean = generator.generate(TestDecoratedGroovyBean.class).newInstance();
-        DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
-        ConventionObject conventionObject = new ConventionObject();
-        new DslObject(dynamicObjectAware).getConvention().getPlugins().put("plugin", conventionObject);
-
-        call("{ it.conventionProperty = 'value' }", bean);
-        assertThat(conventionObject.getConventionProperty(), equalTo("value"));
-        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
-        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
-        assertThat(call("{ it.conventionMethod('value') }", bean), equalTo((Object) "[value]"));
-        assertThat(call("{ it.invokeMethod('conventionMethod', 'value') }", bean), equalTo((Object) "[value]"));
-    }
-
-    @Test
-    public void respectsPropertiesAddedToMetaClassOfJavaObject() throws Exception {
-        Bean bean = generator.generate(Bean.class).newInstance();
-
-        call("{ it.metaClass.getConventionProperty = { -> 'value'} }", bean);
-        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
-        assertThat(call("{ it.getConventionProperty() }", bean), equalTo((Object) "value"));
-        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void respectsPropertiesAddedToMetaClassOfGroovyObject() throws Exception {
-        TestDecoratedGroovyBean bean = generator.generate(TestDecoratedGroovyBean.class).newInstance();
-
-        call("{ it.metaClass.getConventionProperty = { -> 'value'} }", bean);
-        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
-        assertThat(call("{ it.getConventionProperty() }", bean), equalTo((Object) "value"));
-        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void usesExistingGetAsDynamicObjectMethod() throws Exception {
-        DynamicObjectBean bean = generator.generate(DynamicObjectBean.class).newInstance();
-
-        call("{ it.prop = 'value' }", bean);
-        assertThat(call("{ it.prop }", bean), equalTo((Object) "value"));
-
-        bean.getAsDynamicObject().setProperty("prop", "value2");
-        assertThat(call("{ it.prop }", bean), equalTo((Object) "value2"));
-
-        bean.getAsDynamicObject().setProperty("dynamicProp", "value");
-        assertThat(call("{ it.dynamicProp }", bean), equalTo((Object) "value"));
-    }
-
-    @Test
-    public void constructorCanCallGetter() throws Exception {
-        BeanUsesPropertiesInConstructor bean = generator.newInstance(BeanUsesPropertiesInConstructor.class);
-
-        assertThat(bean.name, equalTo("default-name"));
-    }
-
-    @Test
-    public void mixesInSetValueMethodForProperty() throws Exception {
-        BeanWithVariousGettersAndSetters bean = generator.generate(BeanWithVariousGettersAndSetters.class).newInstance();
-
-        call("{ it.prop 'value'}", bean);
-        assertThat(bean.getProp(), equalTo("value"));
-
-        call("{ it.finalGetter 'another'}", bean);
-        assertThat(bean.getFinalGetter(), equalTo("another"));
-
-        call("{ it.writeOnly 12}", bean);
-        assertThat(bean.writeOnly, equalTo(12));
-
-        call("{ it.primitive 12}", bean);
-        assertThat(bean.getPrimitive(), equalTo(12));
-    }
-
-    @Test
-    public void doesNotUseConventionValueOnceSetValueMethodHasBeenCalled() throws Exception {
-        Bean bean = generator.generate(Bean.class).newInstance();
-        IConventionAware conventionAware = (IConventionAware) bean;
-        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
-            public Object call() throws Exception {
-                return "[default]";
-            }
-        });
-
-        assertThat(bean.getProp(), equalTo("[default]"));
-
-        call("{ it.prop 'value'}", bean);
-        assertThat(bean.getProp(), equalTo("value"));
-    }
-
-    @Test
-    public void doesNotMixInSetValueMethodForReadOnlyProperty() throws Exception {
-        BeanWithReadOnlyProperties bean = generator.generate(BeanWithReadOnlyProperties.class).newInstance();
-
-        try {
-            call("{ it.prop 'value'}", bean);
-            fail();
-        } catch (MissingMethodException e) {
-            assertThat(e.getMethod(), equalTo("prop"));
-        }
-    }
-
-    @Test
-    public void doesNotMixInSetValueMethodForMultiValueProperty() throws Exception {
-        CollectionBean bean = generator.generate(CollectionBean.class).newInstance();
-
-        try {
-            call("{ def val = ['value']; it.prop val}", bean);
-            fail();
-        } catch (MissingMethodException e) {
-            assertThat(e.getMethod(), equalTo("prop"));
-        }
-    }
-
-    @Test
-    public void overridesExistingSetValueMethod() throws Exception {
-        BeanWithDslMethods bean = generator.generate(BeanWithDslMethods.class).newInstance();
-        IConventionAware conventionAware = (IConventionAware) bean;
-        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
-            public Object call() throws Exception {
-                return "[default]";
-            }
-        });
-
-        assertThat(bean.getProp(), equalTo("[default]"));
-
-        assertThat(call("{ it.prop 'value'}", bean), sameInstance((Object) bean));
-        assertThat(bean.getProp(), equalTo("[value]"));
-
-        assertThat(call("{ it.prop 1.2}", bean), sameInstance((Object) bean));
-        assertThat(bean.getProp(), equalTo("<1.2>"));
-
-        assertThat(call("{ it.prop 1}", bean), nullValue());
-        assertThat(bean.getProp(), equalTo("<1>"));
-
-        // failing, seems to be that set method override doesn't work for iterables - GRADLE-2097
-        //assertThat(call("{ bean, list -> bean.things(list) }", bean, new LinkedList<Object>()), nullValue());
-        //assertThat(bean.getThings().size(), equalTo(0));
-
-        //assertThat(call("{ bean -> bean.things([1,2,3]) }", bean), nullValue());
-        //assertThat(bean.getThings().size(), equalTo(3));
-
-        //FileCollection files = ProjectBuilder.builder().build().files();
-        //assertThat(call("{ bean, fc -> bean.files fc}", bean, files), nullValue());
-        //assertThat(bean.getFiles(), sameInstance(files));
-    }
-
-    @Test
-    public void mixesInClosureOverloadForActionMethod() throws Exception {
-        Bean bean = generator.generate(Bean.class).newInstance();
-        bean.prop = "value";
-
-        call("{def value; it.doStuff { value = it }; assert value == \'value\' }", bean);
-    }
-
-    @Test
-    public void doesNotOverrideExistingClosureOverload() throws IllegalAccessException, InstantiationException {
-        BeanWithDslMethods bean = generator.generate(BeanWithDslMethods.class).newInstance();
-        bean.prop = "value";
-
-        assertThat(call("{def value; it.doStuff { value = it }; return value }", bean), equalTo((Object) "[value]"));
-    }
-
-    @Test public void generatesDslObjectCompatibleObject() throws Exception {
-        new DslObject(generator.generate(Bean.class).newInstance());
-    }
-
-    public static class Bean {
-        private String prop;
-
-        public String getProp() {
-            return prop;
-        }
-
-        public void setProp(String prop) {
-            this.prop = prop;
-        }
-
-        public String doStuff(String value) {
-            return "{" + value + "}";
-        }
-
-        public void doStuff(Action<String> action) {
-            action.execute(getProp());
-        }
-    }
-
-    public static class BeanWithReadOnlyProperties {
-        public String getProp() {
-            return "value";
-        }
-    }
-
-    public static class CollectionBean {
-        private List<String> prop = new ArrayList<String>();
-
-        public List<String> getProp() {
-            return prop;
-        }
-
-        public void setProp(List<String> prop) {
-            this.prop = prop;
-        }
-    }
-
-    public static class BeanWithConstructor extends Bean {
-        public BeanWithConstructor() {
-            this("default value");
-        }
-
-        public BeanWithConstructor(String value) {
-            setProp(value);
-        }
-
-        public BeanWithConstructor(int value) {
-            setProp(String.valueOf(value));
-        }
-    }
-
-    public static class BeanWithDslMethods extends Bean {
-        private String prop;
-        private FileCollection files;
-        private List<Object> things;
-
-        public String getProp() {
-            return prop;
-        }
-
-        public void setProp(String prop) {
-            this.prop = prop;
-        }
-
-        public FileCollection getFiles() {
-            return files;
-        }
-
-        public void setFiles(FileCollection files) {
-            this.files = files;
-        }
-
-        public List<Object> getThings() {
-            return things;
-        }
-
-        public void setThings(List<Object> things) {
-            this.things = things;
-        }
-
-        public BeanWithDslMethods prop(String property) {
-            this.prop = String.format("[%s]", property);
-            return this;
-        }
-
-        public BeanWithDslMethods prop(Object property) {
-            this.prop = String.format("<%s>", property);
-            return this;
-        }
-
-        public void prop(int property) {
-            this.prop = String.format("<%s>", property);
-        }
-
-        public void doStuff(Closure cl) {
-            cl.call(String.format("[%s]", getProp()));
-        }
-    }
-
-    public static class ConventionAwareBean extends Bean implements IConventionAware, ConventionMapping {
-        public Convention getConvention() {
-            throw new UnsupportedOperationException();
-        }
-
-        public void setConvention(Convention convention) {
-            throw new UnsupportedOperationException();
-        }
-
-        public MappedProperty map(String propertyName, Closure value) {
-            throw new UnsupportedOperationException();
-        }
-
-        public MappedProperty map(String propertyName, Callable<?> value) {
-            throw new UnsupportedOperationException();
-        }
-
-        public <T> T getConventionValue(T actualValue, String propertyName) {
-            if (actualValue instanceof String) {
-                return (T) ("[" + actualValue + "]");
-            } else {
-                throw new UnsupportedOperationException();
-            }
-        }
-
-        public <T> T getConventionValue(T actualValue, String propertyName, boolean isExplicitValue) {
-            return getConventionValue(actualValue, propertyName);
-        }
-
-        public ConventionMapping getConventionMapping() {
-            return this;
-        }
-
-        public void setConventionMapping(ConventionMapping conventionMapping) {
-            throw new UnsupportedOperationException();
-        }
-    }
-
-    public static class DynamicObjectAwareBean extends Bean implements DynamicObjectAware {
-        Convention conv = new ExtensibleDynamicObject(this, ThreadGlobalInstantiator.getOrCreate()).getConvention();
-
-        public Convention getConvention() {
-            return conv;
-        }
-
-        public ExtensionContainer getExtensions() {
-            return conv;
-        }
-
-        public DynamicObject getAsDynamicObject() {
-            return conv.getExtensionsAsDynamicObject();
-        }
-    }
-
-    public static class ConventionObject {
-        private String conventionProperty;
-
-        public String getConventionProperty() {
-            return conventionProperty;
-        }
-
-        public void setConventionProperty(String conventionProperty) {
-            this.conventionProperty = conventionProperty;
-        }
-
-        public Object conventionMethod(String value) {
-            return "[" + value + "]";
-        }
-    }
-
-    public static class BeanWithVariousPropertyTypes {
-        private boolean b;
-
-        public String[] getArrayProperty() {
-            return new String[1];
-        }
-
-        public boolean getBooleanProperty() {
-            return b;
-        }
-
-        public long getLongProperty() {
-            return 12L;
-        }
-
-        public String getReturnValueProperty() {
-            return "value";
-        }
-
-        public BeanWithVariousPropertyTypes setReturnValueProperty(String val) {
-            return this;
-        }
-
-        public void setBooleanProperty(boolean b) {
-            this.b = b;
-        }
-    }
-
-    public static class BeanWithVariousGettersAndSetters extends Bean {
-        private int primitive;
-        private boolean bool;
-        private String finalGetter;
-        private Integer writeOnly;
-
-        public int getPrimitive() {
-            return primitive;
-        }
-
-        public void setPrimitive(int primitive) {
-            this.primitive = primitive;
-        }
-
-        public final String getFinalGetter() {
-            return finalGetter;
-        }
-
-        public void setFinalGetter(String value) {
-            finalGetter = value;
-        }
-
-        public void setWriteOnly(Integer value) {
-            writeOnly = value;
-        }
-    }
-
-    public interface SomeType {
-        String getInterfaceProperty();
-    }
-
-    @NoConventionMapping
-    public static class NoMappingBean implements SomeType {
-        public String getProperty() {
-            return null;
-        }
-
-        public String getInterfaceProperty() {
-            return null;
-        }
-
-        public String getOverriddenProperty() {
-            return null;
-        }
-    }
-
-    @NoDynamicObject
-    public static class NoDynamicBean extends Bean {
-        Object propertyMissing(String name) {
-            return "[" + name + "]";
-        }
-    }
-
-    public static class DynamicObjectBean {
-        private final BeanDynamicObject dynamicObject = new BeanDynamicObject(new Bean());
-
-        public DynamicObject getAsDynamicObject() {
-            return dynamicObject;
-        }
-    }
-
-    public static class BeanSubClass extends NoMappingBean {
-        @Override
-        public String getOverriddenProperty() {
-            return null;
-        }
-
-        public String getOtherProperty() {
-            return null;
-        }
-    }
-
-    public static class BeanUsesPropertiesInConstructor {
-        final String name;
-
-        public BeanUsesPropertiesInConstructor() {
-            name = getName();
-        }
-
-        public String getName() {
-            return "default-name";
-        }
-    }
-
-    public static class UnconstructableBean {
-        static UnsupportedOperationException failure = new UnsupportedOperationException();
-
-        public UnconstructableBean() {
-            throw failure;
-        }
-    }
-
-    public static abstract class AbstractBean {
-        abstract void implementMe();
-    }
-
-    private static class PrivateBean {
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
index d0b93e8..1b465d1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
@@ -20,6 +20,8 @@ import org.junit.Test
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
 import static org.junit.Assert.fail
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.reflect.DirectInstantiator
 
 class AbstractNamedDomainObjectContainerTest {
     private final Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
old mode 100644
new mode 100755
index a9bfb63..082c537
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2009 the original author or authors.
+ * Copyright 2010 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.
@@ -15,9 +15,780 @@
  */
 package org.gradle.api.internal;
 
-public class AsmBackedClassGeneratorTest extends AbstractClassGeneratorTest {
-    @Override
-    protected AsmBackedClassGenerator createGenerator() {
-        return new AsmBackedClassGenerator();
+import groovy.lang.Closure;
+import groovy.lang.GroovyObject;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.ExtensionAware;
+import org.gradle.api.plugins.ExtensionContainer;
+import org.junit.Test;
+import spock.lang.Issue;
+
+import java.util.*;
+import java.util.concurrent.Callable;
+
+import static org.gradle.util.HelperUtil.TEST_CLOSURE;
+import static org.gradle.util.HelperUtil.call;
+import static org.gradle.util.Matchers.isEmpty;
+import static org.gradle.util.WrapUtil.toList;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import static org.gradle.api.internal.AbstractClassGeneratorTestGroovy.*;
+
+public class AsmBackedClassGeneratorTest {
+    private final AbstractClassGenerator generator = new AsmBackedClassGenerator();
+
+    @Test
+    public void mixesInConventionAwareInterface() throws Exception {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertTrue(IConventionAware.class.isAssignableFrom(generatedClass));
+
+        Bean bean = generatedClass.newInstance();
+
+        IConventionAware conventionAware = (IConventionAware) bean;
+        assertThat(conventionAware.getConventionMapping(), instanceOf(ConventionAwareHelper.class));
+        conventionAware.getConventionMapping().map("prop", TEST_CLOSURE);
+    }
+
+    @Test
+    public void mixesInDynamicObjectAwareInterface() throws Exception {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertTrue(DynamicObjectAware.class.isAssignableFrom(generatedClass));
+        Bean bean = generatedClass.newInstance();
+        DynamicObjectAware dynamicBean = (DynamicObjectAware) bean;
+
+        dynamicBean.getAsDynamicObject().setProperty("prop", "value");
+        assertThat(bean.getProp(), equalTo("value"));
+        assertThat(bean.doStuff("some value"), equalTo("{some value}"));
+    }
+
+    @Test
+    public void mixesInExtensionAwareInterface() throws Exception {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertTrue(ExtensionAware.class.isAssignableFrom(generatedClass));
+        Bean bean = generatedClass.newInstance();
+        ExtensionAware dynamicBean = (ExtensionAware) bean;
+
+        assertThat(dynamicBean.getExtensions(), notNullValue());
+    }
+
+    @Test
+    public void mixesInGroovyObjectInterface() throws Exception {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertTrue(GroovyObject.class.isAssignableFrom(generatedClass));
+        Bean bean = generatedClass.newInstance();
+        GroovyObject groovyObject = (GroovyObject) bean;
+        assertThat(groovyObject.getMetaClass(), notNullValue());
+
+        groovyObject.setProperty("prop", "value");
+        assertThat(bean.getProp(), equalTo("value"));
+        assertThat(groovyObject.getProperty("prop"), equalTo((Object) "value"));
+        assertThat(groovyObject.invokeMethod("doStuff", new Object[]{"some value"}), equalTo((Object) "{some value}"));
+    }
+
+    @Test
+    public void cachesGeneratedSubclass() {
+        assertSame(generator.generate(Bean.class), generator.generate(Bean.class));
+    }
+
+    @Test
+    public void doesNotDecorateAlreadyDecoratedClass() {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertSame(generatedClass, generator.generate(generatedClass));
+    }
+
+    @Test
+    public void overridesPublicConstructors() throws Exception {
+        Class<? extends Bean> generatedClass = generator.generate(BeanWithConstructor.class);
+        Bean bean = generatedClass.getConstructor(String.class).newInstance("value");
+        assertThat(bean.getProp(), equalTo("value"));
+
+        bean = generatedClass.getConstructor().newInstance();
+        assertThat(bean.getProp(), equalTo("default value"));
+    }
+
+    @Test
+    public void canConstructInstance() throws Exception {
+        Bean bean = generator.newInstance(BeanWithConstructor.class, "value");
+        assertThat(bean.getClass(), sameInstance((Object) generator.generate(BeanWithConstructor.class)));
+        assertThat(bean.getProp(), equalTo("value"));
+
+        bean = generator.newInstance(BeanWithConstructor.class);
+        assertThat(bean.getProp(), equalTo("default value"));
+
+        bean = generator.newInstance(BeanWithConstructor.class, 127);
+        assertThat(bean.getProp(), equalTo("127"));
+    }
+
+    @Test
+    public void reportsConstructionFailure() {
+        try {
+            generator.newInstance(UnconstructableBean.class);
+            fail();
+        } catch (UnsupportedOperationException e) {
+            assertThat(e, sameInstance(UnconstructableBean.failure));
+        }
+
+        try {
+            generator.newInstance(Bean.class, "arg1", 2);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            generator.newInstance(AbstractBean.class);
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo("Cannot create a proxy class for abstract class 'AbstractBean'."));
+        }
+
+        try {
+            generator.newInstance(PrivateBean.class);
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo("Cannot create a proxy class for private class 'PrivateBean'."));
+        }
+    }
+
+    @Test
+    public void appliesConventionMappingToEachGetter() throws Exception {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertTrue(IConventionAware.class.isAssignableFrom(generatedClass));
+        Bean bean = generatedClass.newInstance();
+        IConventionAware conventionAware = (IConventionAware) bean;
+
+        assertThat(bean.getProp(), nullValue());
+
+        conventionAware.getConventionMapping().map("prop", new Callable<String>() {
+            public String call() {
+                return "conventionValue";
+            }
+        });
+
+        assertThat(bean.getProp(), equalTo("conventionValue"));
+
+        bean.setProp("value");
+        assertThat(bean.getProp(), equalTo("value"));
+
+        bean.setProp(null);
+        assertThat(bean.getProp(), nullValue());
+    }
+
+    @Test
+    @Issue("GRADLE-2163")
+    public void appliesConventionMappingToGroovyBoolean() throws Exception {
+        BeanWithGroovyBoolean bean = generator.generate(BeanWithGroovyBoolean.class).newInstance();
+
+        assertTrue(bean instanceof IConventionAware);
+        assertThat(bean.getSmallB(), equalTo(false));
+        assertThat(bean.getBigB(), nullValue());
+
+        IConventionAware conventionAware = (IConventionAware) bean;
+
+        conventionAware.getConventionMapping().map("smallB", new Callable<Object>() {
+            public Object call() throws Exception {
+                return true;
+            }
+        });
+
+        assertThat(bean.isSmallB(), equalTo(true));
+        assertThat(bean.getSmallB(), equalTo(true));
+
+        bean.setSmallB(false);
+        assertThat(bean.isSmallB(), equalTo(false));
+        assertThat(bean.getSmallB(), equalTo(false));
+
+        conventionAware.getConventionMapping().map("bigB", new Callable<Object>() {
+            public Object call() throws Exception {
+                return Boolean.TRUE;
+            }
+        });
+
+        assertThat(bean.getBigB(), equalTo(Boolean.TRUE));
+        bean.setBigB(Boolean.FALSE);
+        assertThat(bean.getBigB(), equalTo(Boolean.FALSE));
+    }
+
+    @Test
+    public void appliesConventionMappingToCollectionGetter() throws Exception {
+        Class<? extends CollectionBean> generatedClass = generator.generate(CollectionBean.class);
+        CollectionBean bean = generatedClass.newInstance();
+        IConventionAware conventionAware = (IConventionAware) bean;
+        final List<String> conventionValue = toList("value");
+
+        assertThat(bean.getProp(), isEmpty());
+
+        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
+            public Object call() {
+                return conventionValue;
+            }
+        });
+
+        assertThat(bean.getProp(), sameInstance(conventionValue));
+
+        bean.setProp(toList("other"));
+        assertThat(bean.getProp(), equalTo(toList("other")));
+
+        bean.setProp(Collections.<String>emptyList());
+        assertThat(bean.getProp(), equalTo(Collections.<String>emptyList()));
+
+        bean.setProp(null);
+        assertThat(bean.getProp(), nullValue());
+    }
+
+    @Test
+    public void handlesVariousPropertyTypes() throws Exception {
+        BeanWithVariousPropertyTypes bean = generator.generate(BeanWithVariousPropertyTypes.class).newInstance();
+
+        assertThat(bean.getArrayProperty(), notNullValue());
+        assertThat(bean.getBooleanProperty(), equalTo(false));
+        assertThat(bean.getLongProperty(), equalTo(12L));
+        assertThat(bean.setReturnValueProperty("p"), sameInstance(bean));
+
+        IConventionAware conventionAware = (IConventionAware) bean;
+        conventionAware.getConventionMapping().map("booleanProperty", new Callable<Object>() {
+            public Object call() throws Exception {
+                return true;
+            }
+        });
+
+        assertThat(bean.getBooleanProperty(), equalTo(true));
+
+        bean.setBooleanProperty(false);
+        assertThat(bean.getBooleanProperty(), equalTo(false));
+    }
+
+    @Test
+    public void doesNotOverrideMethodsFromConventionAwareInterface() throws Exception {
+        Class<? extends ConventionAwareBean> generatedClass = generator.generate(ConventionAwareBean.class);
+        assertTrue(IConventionAware.class.isAssignableFrom(generatedClass));
+        ConventionAwareBean bean = generatedClass.newInstance();
+        assertSame(bean, bean.getConventionMapping());
+
+        bean.setProp("value");
+        assertEquals("[value]", bean.getProp());
+    }
+
+    @Test
+    public void doesNotOverrideMethodsFromSuperclassesMarkedWithAnnotation() throws Exception {
+        BeanSubClass bean = generator.generate(BeanSubClass.class).newInstance();
+        IConventionAware conventionAware = (IConventionAware) bean;
+        conventionAware.getConventionMapping().map("property", new Callable<Object>() {
+            public Object call() throws Exception {
+                throw new UnsupportedOperationException();
+            }
+        });
+        conventionAware.getConventionMapping().map("interfaceProperty", new Callable<Object>() {
+            public Object call() throws Exception {
+                throw new UnsupportedOperationException();
+            }
+        });
+        conventionAware.getConventionMapping().map("overriddenProperty", new Callable<Object>() {
+            public Object call() throws Exception {
+                return "conventionValue";
+            }
+        });
+        conventionAware.getConventionMapping().map("otherProperty", new Callable<Object>() {
+            public Object call() throws Exception {
+                return "conventionValue";
+            }
+        });
+        assertEquals(null, bean.getProperty());
+        assertEquals(null, bean.getInterfaceProperty());
+        assertEquals("conventionValue", bean.getOverriddenProperty());
+        assertEquals("conventionValue", bean.getOtherProperty());
+    }
+
+    @Test
+    public void doesNotMixInConventionMappingToClassWithAnnotation() throws Exception {
+        NoMappingBean bean = generator.generate(NoMappingBean.class).newInstance();
+        assertFalse(bean instanceof IConventionAware);
+        assertNull(bean.getInterfaceProperty());
+
+        // Check dynamic object behaviour still works
+        assertTrue(bean instanceof DynamicObjectAware);
+    }
+
+    @Test
+    public void doesNotOverrideMethodsFromDynamicObjectAwareInterface() throws Exception {
+        DynamicObjectAwareBean bean = generator.generate(DynamicObjectAwareBean.class).newInstance();
+        assertThat(bean.getConvention(), sameInstance(bean.conv));
+        assertThat(bean.getAsDynamicObject(), sameInstance(bean.conv.getExtensionsAsDynamicObject()));
+    }
+
+    @Test
+    public void canAddDynamicPropertiesAndMethodsToJavaObject() throws Exception {
+        Bean bean = generator.generate(Bean.class).newInstance();
+        DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
+        ConventionObject conventionObject = new ConventionObject();
+        new DslObject(dynamicObjectAware).getConvention().getPlugins().put("plugin", conventionObject);
+
+        call("{ it.conventionProperty = 'value' }", bean);
+        assertThat(conventionObject.getConventionProperty(), equalTo("value"));
+        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
+        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
+        assertThat(call("{ it.conventionMethod('value') }", bean), equalTo((Object) "[value]"));
+        assertThat(call("{ it.invokeMethod('conventionMethod', 'value') }", bean), equalTo((Object) "[value]"));
+    }
+
+    @Test
+    public void canAddDynamicPropertiesAndMethodsToGroovyObject() throws Exception {
+        TestDecoratedGroovyBean bean = generator.generate(TestDecoratedGroovyBean.class).newInstance();
+        DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
+        ConventionObject conventionObject = new ConventionObject();
+        new DslObject(dynamicObjectAware).getConvention().getPlugins().put("plugin", conventionObject);
+
+        call("{ it.conventionProperty = 'value' }", bean);
+        assertThat(conventionObject.getConventionProperty(), equalTo("value"));
+        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
+        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
+        assertThat(call("{ it.conventionMethod('value') }", bean), equalTo((Object) "[value]"));
+        assertThat(call("{ it.invokeMethod('conventionMethod', 'value') }", bean), equalTo((Object) "[value]"));
+    }
+
+    @Test
+    public void respectsPropertiesAddedToMetaClassOfJavaObject() throws Exception {
+        Bean bean = generator.generate(Bean.class).newInstance();
+
+        call("{ it.metaClass.getConventionProperty = { -> 'value'} }", bean);
+        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
+        assertThat(call("{ it.getConventionProperty() }", bean), equalTo((Object) "value"));
+        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void respectsPropertiesAddedToMetaClassOfGroovyObject() throws Exception {
+        TestDecoratedGroovyBean bean = generator.generate(TestDecoratedGroovyBean.class).newInstance();
+
+        call("{ it.metaClass.getConventionProperty = { -> 'value'} }", bean);
+        assertThat(call("{ it.hasProperty('conventionProperty') }", bean), notNullValue());
+        assertThat(call("{ it.getConventionProperty() }", bean), equalTo((Object) "value"));
+        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void usesExistingGetAsDynamicObjectMethod() throws Exception {
+        DynamicObjectBean bean = generator.generate(DynamicObjectBean.class).newInstance();
+
+        call("{ it.prop = 'value' }", bean);
+        assertThat(call("{ it.prop }", bean), equalTo((Object) "value"));
+
+        bean.getAsDynamicObject().setProperty("prop", "value2");
+        assertThat(call("{ it.prop }", bean), equalTo((Object) "value2"));
+
+        bean.getAsDynamicObject().setProperty("dynamicProp", "value");
+        assertThat(call("{ it.dynamicProp }", bean), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void constructorCanCallGetter() throws Exception {
+        BeanUsesPropertiesInConstructor bean = generator.newInstance(BeanUsesPropertiesInConstructor.class);
+
+        assertThat(bean.name, equalTo("default-name"));
+    }
+
+    @Test
+    public void mixesInSetValueMethodForSingleValuedProperty() throws Exception {
+        BeanWithVariousGettersAndSetters bean = generator.generate(BeanWithVariousGettersAndSetters.class).newInstance();
+
+        call("{ it.prop 'value'}", bean);
+        assertThat(bean.getProp(), equalTo("value"));
+
+        call("{ it.finalGetter 'another'}", bean);
+        assertThat(bean.getFinalGetter(), equalTo("another"));
+
+        call("{ it.writeOnly 12}", bean);
+        assertThat(bean.writeOnly, equalTo(12));
+
+        call("{ it.primitive 12}", bean);
+        assertThat(bean.getPrimitive(), equalTo(12));
+    }
+
+    @Test
+    public void doesNotUseConventionValueOnceSetValueMethodHasBeenCalled() throws Exception {
+        Bean bean = generator.generate(Bean.class).newInstance();
+        IConventionAware conventionAware = (IConventionAware) bean;
+        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
+            public Object call() throws Exception {
+                return "[default]";
+            }
+        });
+
+        assertThat(bean.getProp(), equalTo("[default]"));
+
+        call("{ it.prop 'value'}", bean);
+        assertThat(bean.getProp(), equalTo("value"));
+    }
+
+    @Test
+    public void doesNotMixInSetValueMethodForReadOnlyProperty() throws Exception {
+        BeanWithReadOnlyProperties bean = generator.generate(BeanWithReadOnlyProperties.class).newInstance();
+
+        try {
+            call("{ it.prop 'value'}", bean);
+            fail();
+        } catch (MissingMethodException e) {
+            assertThat(e.getMethod(), equalTo("prop"));
+        }
+    }
+
+    @Test
+    public void doesNotMixInSetValueMethodForMultiValueProperty() throws Exception {
+        CollectionBean bean = generator.generate(CollectionBean.class).newInstance();
+
+        try {
+            call("{ def val = ['value']; it.prop val}", bean);
+            fail();
+        } catch (MissingMethodException e) {
+            assertThat(e.getMethod(), equalTo("prop"));
+        }
+    }
+
+    @Test
+    public void overridesExistingSetValueMethod() throws Exception {
+        BeanWithDslMethods bean = generator.generate(BeanWithDslMethods.class).newInstance();
+        IConventionAware conventionAware = (IConventionAware) bean;
+        conventionAware.getConventionMapping().map("prop", new Callable<Object>() {
+            public Object call() throws Exception {
+                return "[default]";
+            }
+        });
+
+        assertThat(bean.getProp(), equalTo("[default]"));
+
+        assertThat(call("{ it.prop 'value'}", bean), sameInstance((Object) bean));
+        assertThat(bean.getProp(), equalTo("[value]"));
+
+        assertThat(call("{ it.prop 1.2}", bean), sameInstance((Object) bean));
+        assertThat(bean.getProp(), equalTo("<1.2>"));
+
+        assertThat(call("{ it.prop 1}", bean), nullValue());
+        assertThat(bean.getProp(), equalTo("<1>"));
+
+        // failing, seems to be that set method override doesn't work for iterables - GRADLE-2097
+        //assertThat(call("{ bean, list -> bean.things(list) }", bean, new LinkedList<Object>()), nullValue());
+        //assertThat(bean.getThings().size(), equalTo(0));
+
+        //assertThat(call("{ bean -> bean.things([1,2,3]) }", bean), nullValue());
+        //assertThat(bean.getThings().size(), equalTo(3));
+
+        //FileCollection files = ProjectBuilder.builder().build().files();
+        //assertThat(call("{ bean, fc -> bean.files fc}", bean, files), nullValue());
+        //assertThat(bean.getFiles(), sameInstance(files));
+    }
+
+    @Test
+    public void mixesInClosureOverloadForActionMethod() throws Exception {
+        Bean bean = generator.generate(Bean.class).newInstance();
+        bean.prop = "value";
+
+        call("{def value; it.doStuff { value = it }; assert value == \'value\' }", bean);
+    }
+
+    @Test
+    public void doesNotOverrideExistingClosureOverload() throws IllegalAccessException, InstantiationException {
+        BeanWithDslMethods bean = generator.generate(BeanWithDslMethods.class).newInstance();
+        bean.prop = "value";
+
+        assertThat(call("{def value; it.doStuff { value = it }; return value }", bean), equalTo((Object) "[value]"));
+    }
+
+    @Test public void generatesDslObjectCompatibleObject() throws Exception {
+        new DslObject(generator.generate(Bean.class).newInstance());
+    }
+
+    public static class Bean {
+        private String prop;
+
+        public String getProp() {
+            return prop;
+        }
+
+        public void setProp(String prop) {
+            this.prop = prop;
+        }
+
+        public String doStuff(String value) {
+            return "{" + value + "}";
+        }
+
+        public void doStuff(Action<String> action) {
+            action.execute(getProp());
+        }
+    }
+
+    public static class BeanWithReadOnlyProperties {
+        public String getProp() {
+            return "value";
+        }
+    }
+
+    public static class CollectionBean {
+        private List<String> prop = new ArrayList<String>();
+
+        public List<String> getProp() {
+            return prop;
+        }
+
+        public void setProp(List<String> prop) {
+            this.prop = prop;
+        }
+    }
+
+    public static class BeanWithConstructor extends Bean {
+        public BeanWithConstructor() {
+            this("default value");
+        }
+
+        public BeanWithConstructor(String value) {
+            setProp(value);
+        }
+
+        public BeanWithConstructor(int value) {
+            setProp(String.valueOf(value));
+        }
+    }
+
+    public static class BeanWithDslMethods extends Bean {
+        private String prop;
+        private FileCollection files;
+        private List<Object> things;
+
+        public String getProp() {
+            return prop;
+        }
+
+        public void setProp(String prop) {
+            this.prop = prop;
+        }
+
+        public FileCollection getFiles() {
+            return files;
+        }
+
+        public void setFiles(FileCollection files) {
+            this.files = files;
+        }
+
+        public List<Object> getThings() {
+            return things;
+        }
+
+        public void setThings(List<Object> things) {
+            this.things = things;
+        }
+
+        public BeanWithDslMethods prop(String property) {
+            this.prop = String.format("[%s]", property);
+            return this;
+        }
+
+        public BeanWithDslMethods prop(Object property) {
+            this.prop = String.format("<%s>", property);
+            return this;
+        }
+
+        public void prop(int property) {
+            this.prop = String.format("<%s>", property);
+        }
+
+        public void doStuff(Closure cl) {
+            cl.call(String.format("[%s]", getProp()));
+        }
+    }
+
+    public static class ConventionAwareBean extends Bean implements IConventionAware, ConventionMapping {
+        public Convention getConvention() {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setConvention(Convention convention) {
+            throw new UnsupportedOperationException();
+        }
+
+        public MappedProperty map(String propertyName, Closure value) {
+            throw new UnsupportedOperationException();
+        }
+
+        public MappedProperty map(String propertyName, Callable<?> value) {
+            throw new UnsupportedOperationException();
+        }
+
+        public <T> T getConventionValue(T actualValue, String propertyName) {
+            if (actualValue instanceof String) {
+                return (T) ("[" + actualValue + "]");
+            } else {
+                throw new UnsupportedOperationException();
+            }
+        }
+
+        public <T> T getConventionValue(T actualValue, String propertyName, boolean isExplicitValue) {
+            return getConventionValue(actualValue, propertyName);
+        }
+
+        public ConventionMapping getConventionMapping() {
+            return this;
+        }
+
+        public void setConventionMapping(ConventionMapping conventionMapping) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public static class DynamicObjectAwareBean extends Bean implements DynamicObjectAware {
+        Convention conv = new ExtensibleDynamicObject(this, ThreadGlobalInstantiator.getOrCreate()).getConvention();
+
+        public Convention getConvention() {
+            return conv;
+        }
+
+        public ExtensionContainer getExtensions() {
+            return conv;
+        }
+
+        public DynamicObject getAsDynamicObject() {
+            return conv.getExtensionsAsDynamicObject();
+        }
+    }
+
+    public static class ConventionObject {
+        private String conventionProperty;
+
+        public String getConventionProperty() {
+            return conventionProperty;
+        }
+
+        public void setConventionProperty(String conventionProperty) {
+            this.conventionProperty = conventionProperty;
+        }
+
+        public Object conventionMethod(String value) {
+            return "[" + value + "]";
+        }
+    }
+
+    public static class BeanWithVariousPropertyTypes {
+        private boolean b;
+
+        public String[] getArrayProperty() {
+            return new String[1];
+        }
+
+        public boolean getBooleanProperty() {
+            return b;
+        }
+
+        public long getLongProperty() {
+            return 12L;
+        }
+
+        public String getReturnValueProperty() {
+            return "value";
+        }
+
+        public BeanWithVariousPropertyTypes setReturnValueProperty(String val) {
+            return this;
+        }
+
+        public void setBooleanProperty(boolean b) {
+            this.b = b;
+        }
+    }
+
+    public static class BeanWithVariousGettersAndSetters extends Bean {
+        private int primitive;
+        private boolean bool;
+        private String finalGetter;
+        private Integer writeOnly;
+
+        public int getPrimitive() {
+            return primitive;
+        }
+
+        public void setPrimitive(int primitive) {
+            this.primitive = primitive;
+        }
+
+        public final String getFinalGetter() {
+            return finalGetter;
+        }
+
+        public void setFinalGetter(String value) {
+            finalGetter = value;
+        }
+
+        public void setWriteOnly(Integer value) {
+            writeOnly = value;
+        }
+    }
+
+    public interface SomeType {
+        String getInterfaceProperty();
+    }
+
+    @NoConventionMapping
+    public static class NoMappingBean implements SomeType {
+        public String getProperty() {
+            return null;
+        }
+
+        public String getInterfaceProperty() {
+            return null;
+        }
+
+        public String getOverriddenProperty() {
+            return null;
+        }
+    }
+
+    public static class DynamicObjectBean {
+        private final BeanDynamicObject dynamicObject = new BeanDynamicObject(new Bean());
+
+        public DynamicObject getAsDynamicObject() {
+            return dynamicObject;
+        }
+    }
+
+    public static class BeanSubClass extends NoMappingBean {
+        @Override
+        public String getOverriddenProperty() {
+            return null;
+        }
+
+        public String getOtherProperty() {
+            return null;
+        }
+    }
+
+    public static class BeanUsesPropertiesInConstructor {
+        final String name;
+
+        public BeanUsesPropertiesInConstructor() {
+            name = getName();
+        }
+
+        public String getName() {
+            return "default-name";
+        }
+    }
+
+    public static class UnconstructableBean {
+        static UnsupportedOperationException failure = new UnsupportedOperationException();
+
+        public UnconstructableBean() {
+            throw failure;
+        }
+    }
+
+    public static abstract class AbstractBean {
+        abstract void implementMe();
+    }
+
+    private static class PrivateBean {
     }
-}
\ No newline at end of file
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiatorTest.groovy
index 3b53a6f..082095e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiatorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ClassGeneratorBackedInstantiatorTest.groovy
@@ -16,6 +16,7 @@
 package org.gradle.api.internal
 
 import spock.lang.Specification
+import org.gradle.internal.reflect.Instantiator
 
 class ClassGeneratorBackedInstantiatorTest extends Specification {
     final ClassGenerator classGenerator = Mock()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultClassPathRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultClassPathRegistryTest.groovy
index 37659f1..39568bf 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultClassPathRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultClassPathRegistryTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.internal
 
 import spock.lang.Specification
-import org.gradle.util.ClassPath
+import org.gradle.internal.classpath.ClassPath
 
 class DefaultClassPathRegistryTest extends Specification {
     final ClassPathProvider provider1 = Mock()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectListTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectListTest.groovy
index 04db660..531e5b9 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectListTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectListTest.groovy
@@ -19,6 +19,7 @@ import spock.lang.Specification
 import org.gradle.api.Namer
 import org.gradle.api.Action
 import org.gradle.api.InvalidUserDataException
+import org.gradle.internal.reflect.DirectInstantiator
 
 class DefaultNamedDomainObjectListTest extends Specification {
     final Namer<Object> toStringNamer = new Namer<Object>() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java
index ab695fc..514c216 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectSetTest.java
@@ -41,7 +41,7 @@ import static org.junit.Assert.*;
 
 @RunWith(JMock.class)
 public class DefaultNamedDomainObjectSetTest {
-    private final Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator());
+    private final org.gradle.internal.reflect.Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new org.gradle.internal.reflect.DirectInstantiator());
     private final Namer<Bean> namer = new Namer<Bean>() { public String determineName(Bean bean) { return bean.name; } };
     @SuppressWarnings("unchecked")
     private final DefaultNamedDomainObjectSet<Bean> container = instantiator.newInstance(DefaultNamedDomainObjectSet.class, Bean.class, instantiator, namer);
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyClassPathProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyClassPathProviderTest.groovy
index bc34cf8..815ba2b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyClassPathProviderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DependencyClassPathProviderTest.groovy
@@ -19,7 +19,7 @@ import org.gradle.api.internal.classpath.Module
 import org.gradle.api.internal.classpath.ModuleRegistry
 import spock.lang.Specification
 import org.gradle.api.internal.classpath.PluginModuleRegistry
-import org.gradle.util.DefaultClassPath
+import org.gradle.internal.classpath.DefaultClassPath
 
 class DependencyClassPathProviderTest extends Specification {
     final ModuleRegistry moduleRegistry = Mock()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DirectInstantiatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DirectInstantiatorTest.groovy
deleted file mode 100644
index 89c28f8..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DirectInstantiatorTest.groovy
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal
-
-import spock.lang.Specification
-
-import org.gradle.internal.UncheckedException
-
-class DirectInstantiatorTest extends Specification {
-    final DirectInstantiator instantiator = new DirectInstantiator()
-
-    def "creates instance with constructor parameters"() {
-        CharSequence param = Mock()
-
-        expect:
-        def result = instantiator.newInstance(SomeType, param)
-        result instanceof SomeType
-        result.result == param
-    }
-
-    def "creates instance with subtypes of constructor parameters"() {
-        expect:
-        def result = instantiator.newInstance(SomeType, "param")
-        result instanceof SomeType
-        result.result == "param"
-    }
-
-    def "creates instance with null constructor parameters"() {
-        expect:
-        def result = instantiator.newInstance(SomeType, [null] as Object[])
-        result instanceof SomeType
-        result.result == null
-    }
-
-    def "uses constructor which matches parameter types"() {
-        expect:
-        def stringResult = instantiator.newInstance(SomeTypeWithMultipleConstructors, "param")
-        stringResult instanceof SomeTypeWithMultipleConstructors
-        stringResult.result == "param"
-
-        and:
-        def numberResult = instantiator.newInstance(SomeTypeWithMultipleConstructors, 5)
-        numberResult instanceof SomeTypeWithMultipleConstructors
-        numberResult.result == 5
-    }
-
-    def "unboxes constructor parameters"() {
-        expect:
-        def result = instantiator.newInstance(SomeTypeWithPrimitiveTypes, true)
-        result instanceof SomeTypeWithPrimitiveTypes
-        result.result
-    }
-
-    def "fails when target class has ambiguous constructor"() {
-        when:
-        instantiator.newInstance(TypeWithAmbiguousConstructor, "param")
-
-        then:
-        IllegalArgumentException e = thrown()
-        e.message == "Found multiple public constructors for ${TypeWithAmbiguousConstructor} which accept parameters [param]."
-
-        when:
-        instantiator.newInstance(TypeWithAmbiguousConstructor, true)
-
-        then:
-        e = thrown()
-        e.message == "Found multiple public constructors for ${TypeWithAmbiguousConstructor} which accept parameters [true]."
-    }
-
-    def "fails when target class has no matching public constructor"() {
-        def param = new Object()
-
-        when:
-        instantiator.newInstance(SomeType, param)
-
-        then:
-        IllegalArgumentException e = thrown()
-        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [${param}]."
-
-        when:
-        instantiator.newInstance(SomeType, "a", "b")
-
-        then:
-        e = thrown()
-        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [a, b]."
-
-        when:
-        instantiator.newInstance(SomeType, false)
-
-        then:
-        e = thrown()
-        e.message == "Could not find any public constructor for ${SomeType} which accepts parameters [false]."
-
-        when:
-
-        instantiator.newInstance(SomeTypeWithPrimitiveTypes, "a")
-
-        then:
-        e = thrown()
-        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [a]."
-
-        when:
-
-        instantiator.newInstance(SomeTypeWithPrimitiveTypes, 22)
-
-        then:
-        e = thrown()
-        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [22]."
-
-        when:
-
-        instantiator.newInstance(SomeTypeWithPrimitiveTypes, [null] as Object[])
-
-        then:
-        e = thrown()
-        e.message == "Could not find any public constructor for ${SomeTypeWithPrimitiveTypes} which accepts parameters [null]."
-    }
-
-    def "rethrows unchecked exception thrown by constructor"() {
-        when:
-        instantiator.newInstance(BrokenType, false)
-
-        then:
-        RuntimeException e = thrown()
-        e.message == 'broken'
-    }
-
-    def "wraps checked exception thrown by constructor"() {
-        when:
-        instantiator.newInstance(BrokenType, true)
-
-        then:
-        UncheckedException e = thrown()
-        e.cause.message == 'broken'
-    }
-}
-
-class SomeType {
-    final Object result
-
-    SomeType(CharSequence result) {
-        this.result = result
-    }
-}
-
-class BrokenType {
-    BrokenType(Boolean checked) {
-        throw checked ? new Exception("broken") : new RuntimeException("broken")
-    }
-}
-
-class SomeTypeWithMultipleConstructors {
-    final Object result
-
-    SomeTypeWithMultipleConstructors(CharSequence result) {
-        this.result = result
-    }
-
-    SomeTypeWithMultipleConstructors(Number result) {
-        this.result = result
-    }
-}
-
-class SomeTypeWithPrimitiveTypes {
-    final Object result
-
-    SomeTypeWithPrimitiveTypes(boolean result) {
-        this.result = result
-    }
-}
-
-class TypeWithAmbiguousConstructor {
-    TypeWithAmbiguousConstructor(CharSequence param) {
-    }
-
-    TypeWithAmbiguousConstructor(String param) {
-    }
-
-    TypeWithAmbiguousConstructor(Boolean param) {
-    }
-
-    TypeWithAmbiguousConstructor(boolean param) {
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy
index 1a2a7d7..e42e0cb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/FactoryNamedDomainObjectContainerSpec.groovy
@@ -19,6 +19,7 @@ import org.gradle.api.Named
 import org.gradle.api.NamedDomainObjectFactory
 import org.gradle.api.Namer
 import spock.lang.Specification
+import org.gradle.internal.reflect.Instantiator
 
 class FactoryNamedDomainObjectContainerSpec extends Specification {
     final NamedDomainObjectFactory<String> factory = Mock()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy
index 2a0a5e3..5e01d3f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/NestedConfigureAutoCreateNamedDomainObjectContainerSpec.groovy
@@ -16,6 +16,7 @@
 package org.gradle.api.internal
 
 import spock.lang.*
+import org.gradle.internal.reflect.DirectInstantiator
 
 class NestedConfigureAutoCreateNamedDomainObjectContainerSpec extends Specification {
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
index 1f64c17..43fa12b 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/TestContainer.java
@@ -17,7 +17,7 @@ package org.gradle.api.internal;
 
 public class TestContainer extends AbstractNamedDomainObjectContainer<TestObject> {
 
-    public TestContainer(Instantiator instantiator) {
+    public TestContainer(org.gradle.internal.reflect.Instantiator instantiator) {
         super(TestObject.class, instantiator, new DynamicPropertyNamer());
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
index 58860b1..cfeb3a7 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
@@ -23,7 +23,7 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.ArtifactRepositoryContainer
 import org.gradle.api.artifacts.UnknownRepositoryException
 import org.gradle.api.artifacts.repositories.ArtifactRepository
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal
 import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.integration.junit4.JMock
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
index 03bb9ee..ee17fdc 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal.artifacts.configurations;
 
 import org.gradle.api.artifacts.UnknownConfigurationException
 import org.gradle.api.internal.DomainObjectContext
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
 import org.gradle.listener.ListenerManager
 import org.gradle.util.HelperUtil
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
index 7cef8b0..8a3b896 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
@@ -28,6 +28,8 @@ import org.junit.runner.RunWith
 import org.gradle.api.internal.*
 import static org.junit.Assert.*
 import static org.hamcrest.Matchers.*
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.internal.reflect.DirectInstantiator
 
 /**
  * @author Hans Dockter
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
index 9f067e3..cb5772d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.api.artifacts.ArtifactRepositoryContainer
 import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository
-import org.gradle.api.internal.DirectInstantiator
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainerTest
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal
 import org.jmock.integration.junit4.JMock
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
index 55baf9f..9a6d5fc 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
@@ -16,7 +16,7 @@
 package org.gradle.api.internal.changedetection;
 
 import org.gradle.cache.PersistentIndexedCache;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.TemporaryFolder;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
index c784909..29d1433 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
@@ -23,9 +23,9 @@ import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.internal.DefaultCacheRepository;
+import org.gradle.internal.id.RandomLongIdGenerator;
 import org.gradle.testfixtures.internal.InMemoryCacheFactory;
 import org.gradle.util.HelperUtil;
-import org.gradle.util.RandomLongIdGenerator;
 import org.gradle.util.TemporaryFolder;
 import org.gradle.util.TestFile;
 import org.hamcrest.Matcher;
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
index 5be392d..ecec7f5 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
@@ -83,7 +83,7 @@ public class ShortCircuitTaskArtifactStateRepositoryTest {
 
         TaskArtifactState state = repository.getStateFor(task);
 
-        startParameter.setNoOpt(true);
+        startParameter.setRerunTasks(true);
         assertFalse(state.isUpToDate());
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistryTest.groovy
deleted file mode 100644
index 3a246e6..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/concurrent/SynchronizedServiceRegistryTest.groovy
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal.concurrent;
-
-
-import org.gradle.internal.service.ServiceRegistry
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 11/24/11
- */
-public class SynchronizedServiceRegistryTest extends Specification {
-
-    def delegate = Mock(ServiceRegistry)
-    def reg = new SynchronizedServiceRegistry(delegate);
-
-    def "gets services from delegate"() {
-        when:
-        reg.get(Object)
-        then:
-        1 * delegate.get(Object)
-        when:
-        reg.getFactory(String)
-        then:
-        1 * delegate.getFactory(String)
-        when:
-        reg.newInstance(Integer)
-        then:
-        1 * delegate.newInstance(Integer)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
index 3d1f84d..317ee9f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverSpec.groovy
@@ -154,7 +154,7 @@ class BaseDirFileResolverSpec extends Specification {
         normalize(root) == root
 
         where:
-        root << File.listRoots()
+        root << getFsRoots()
     }
 
     @Requires(TestPrecondition.WINDOWS)
@@ -168,7 +168,7 @@ class BaseDirFileResolverSpec extends Specification {
     }
 
     def "normalizes relative path that refers to ancestor of file system root"() {
-        File root = File.listRoots()[0]
+        File root = getFsRoots()[0]
 
         expect:
         normalize("../../..", root) == root
@@ -191,4 +191,8 @@ class BaseDirFileResolverSpec extends Specification {
     def normalize(Object path, File baseDir = tmpDir.dir) {
         new BaseDirFileResolver(FileSystems.default, baseDir).resolve(path)
     }
+
+    private File[] getFsRoots() {
+        File.listRoots().findAll { !it.absolutePath.startsWith("A:") }
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
index 21a06b4..8202960 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/BaseDirFileResolverTest.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.internal.file
 
-import java.util.concurrent.Callable
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.PathValidation
 import org.gradle.api.file.FileCollection
@@ -28,6 +27,9 @@ import org.gradle.util.TestPrecondition
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+
+import java.util.concurrent.Callable
+
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
 
@@ -321,9 +323,9 @@ class BaseDirFileResolverTest {
     @Test public void testResolveLater() {
         String src;
         Closure cl = { src }
-        FileSource source = baseDirConverter.resolveLater(cl)
+        org.gradle.internal.Factory<File> source = baseDirConverter.resolveLater(cl)
         src = 'file1'
-        assertEquals(new File(baseDir, 'file1'), source.get())
+        assertEquals(new File(baseDir, 'file1'), source.create())
     }
     
     @Test public void testCreateFileResolver() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
index c58d2d9..74201e5 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
@@ -19,13 +19,14 @@ import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 
 import spock.lang.Specification
+import org.gradle.internal.Factory
 
 class DefaultTemporaryFileProviderTest extends Specification {
     @Rule TemporaryFolder tmpDir
     DefaultTemporaryFileProvider provider
 
     def setup() {
-        provider = new DefaultTemporaryFileProvider({tmpDir.dir} as FileSource)
+        provider = new DefaultTemporaryFileProvider({tmpDir.dir} as Factory)
     }
 
     def "allocates temp file"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
index d4700d1..db488eb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
@@ -19,6 +19,7 @@ import org.gradle.api.Action;
 import org.gradle.api.file.FileCopyDetails;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
+import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 import org.gradle.util.HelperUtil;
 import org.gradle.util.TemporaryFolder;
 import org.gradle.util.TestFile;
@@ -35,21 +36,24 @@ import org.junit.runner.RunWith;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
 
 import static org.gradle.util.Matchers.*;
-import static org.gradle.util.WrapUtil.*;
+import static org.gradle.util.WrapUtil.toList;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 @RunWith(JMock.class)
 public class MappingCopySpecVisitorTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
+
     @Rule
     public final TemporaryFolder tmpDir = new TemporaryFolder();
     private final CopySpecVisitor delegate = context.mock(CopySpecVisitor.class);
-    private final MappingCopySpecVisitor visitor = new MappingCopySpecVisitor(delegate);
     private final ReadableCopySpec spec = context.mock(ReadableCopySpec.class);
     private final FileVisitDetails details = context.mock(FileVisitDetails.class);
+    private final FileSystem fileSystem = context.mock(FileSystem.class);
+    private final MappingCopySpecVisitor visitor = new MappingCopySpecVisitor(delegate, fileSystem);
 
     @Test
     public void delegatesStartAndEndVisitMethods() {
@@ -85,7 +89,7 @@ public class MappingCopySpecVisitorTest {
         final Collector<Object> collectDetails2 = collector();
         final Collector<Object> collectDetails3 = collector();
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             Sequence seq = context.sequence("seq");
             one(delegate).visitSpec(spec);
             inSequence(seq);
@@ -117,7 +121,7 @@ public class MappingCopySpecVisitorTest {
     public void initialRelativePathForFileIsSpecPathPlusFilePath() {
         FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(spec).getDestPath();
             will(returnValue(new RelativePath(false, "spec")));
             allowing(details).getRelativePath();
@@ -126,12 +130,12 @@ public class MappingCopySpecVisitorTest {
 
         assertThat(copyDetails.getRelativePath(), equalTo(new RelativePath(true, "spec", "file")));
     }
-    
+
     @Test
     public void relativePathForDirIsSpecPathPlusFilePath() {
         FileVisitDetails visitDetails = expectSpecAndDirVisited();
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(spec).getDestPath();
             will(returnValue(new RelativePath(false, "spec")));
             allowing(details).getRelativePath();
@@ -163,7 +167,7 @@ public class MappingCopySpecVisitorTest {
         @SuppressWarnings("unchecked")
         final Action<FileCopyDetails> action2 = context.mock(Action.class, "action2");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             Sequence seq = context.sequence("seq");
             one(delegate).visitSpec(spec);
             inSequence(seq);
@@ -204,6 +208,7 @@ public class MappingCopySpecVisitorTest {
         mappedDetails.setMode(0644);
 
         context.checking(new Expectations() {{
+
             one(details).open();
             will(returnValue(new ByteArrayInputStream("content".getBytes())));
             one(details).isDirectory();
@@ -220,6 +225,21 @@ public class MappingCopySpecVisitorTest {
     }
 
     @Test
+    public void explicitFileModeDefinitionIsAppliedToTarget() throws IOException {
+        final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
+        final TestFile destFile = tmpDir.getDir().file("test.txt").createFile();
+
+        // set file permissions explicitly
+        mappedDetails.setMode(0645);
+        context.checking(new Expectations() {{
+            one(details).copyTo(destFile);
+            will(returnValue(true));
+            one(fileSystem).chmod(destFile, 0645);
+        }});
+        mappedDetails.copyTo(destFile);
+    }
+
+    @Test
     public void getSizeReturnsSizeOfFilteredContent() {
         final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
 
@@ -250,7 +270,7 @@ public class MappingCopySpecVisitorTest {
     public void permissionsArePreservedByDefault() {
         FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             one(details).isDirectory();
             will(returnValue(true));
 
@@ -268,7 +288,7 @@ public class MappingCopySpecVisitorTest {
     public void filePermissionsCanBeOverriddenBySpec() {
         FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             one(details).isDirectory();
             will(returnValue(false));
 
@@ -279,11 +299,12 @@ public class MappingCopySpecVisitorTest {
         assertThat(copyDetails.getMode(), equalTo(234));
     }
 
+
     @Test
     public void directoryPermissionsCanBeOverriddenBySpec() {
         FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             one(details).isDirectory();
             will(returnValue(true));
 
@@ -325,7 +346,7 @@ public class MappingCopySpecVisitorTest {
         @SuppressWarnings("unchecked")
         final Action<FileCopyDetails> action = context.mock(Action.class, "action1");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             Sequence seq = context.sequence("seq");
             one(delegate).visitSpec(spec);
             inSequence(seq);
@@ -376,5 +397,4 @@ public class MappingCopySpecVisitorTest {
             }
         };
     }
-    
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
index 769bd53..99c255a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.plugins
 
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.api.plugins.Convention
 import org.gradle.api.plugins.TestPluginConvention1
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
index 4f19dc3..efae86b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
@@ -55,6 +55,7 @@ import org.gradle.api.*
 import org.gradle.api.internal.*
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
+import org.gradle.internal.reflect.Instantiator
 
 /**
  * @author Hans Dockter
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java
index bedab2a..82c4f44 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java
@@ -117,7 +117,7 @@ public class GlobalServicesRegistryTest {
     
     @Test
     public void providesAnInstantiator() {
-        assertThat(registry.get(Instantiator.class), instanceOf(ClassGeneratorBackedInstantiator.class));
+        assertThat(registry.get(org.gradle.internal.reflect.Instantiator.class), instanceOf(ClassGeneratorBackedInstantiator.class));
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
index a78648a..2a16ae5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
@@ -21,7 +21,6 @@ import org.gradle.StartParameter;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
 import org.gradle.api.initialization.ProjectDescriptor;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.api.internal.Instantiator;
 import org.gradle.groovy.scripts.StringScriptSource;
 import org.gradle.groovy.scripts.UriScriptSource;
 import org.gradle.internal.Factory;
@@ -59,7 +58,7 @@ public class ProjectFactoryTest {
     private RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
     private StartParameter startParameterStub = new StartParameter();
     private ServiceRegistryFactory serviceRegistryFactory = new TestTopLevelBuildServiceRegistry(new GlobalTestServices(), startParameterStub, rootDir);
-    private Instantiator instantiatorMock = serviceRegistryFactory.get(Instantiator.class);
+    private org.gradle.internal.reflect.Instantiator instantiatorMock = serviceRegistryFactory.get(org.gradle.internal.reflect.Instantiator.class);
     private GradleInternal gradle = context.mock(GradleInternal.class);
 
     private ProjectFactory projectFactory;
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
index c989f5a..e615e59 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
@@ -90,8 +90,8 @@ public class ProjectInternalServiceRegistryTest {
             will(returnValue(pluginRegistry));
             allowing(parent).get(DependencyManagementServices.class);
             will(returnValue(dependencyManagementServices));
-            allowing(parent).get(Instantiator.class);
-            will(returnValue(new DirectInstantiator()));
+            allowing(parent).get(org.gradle.internal.reflect.Instantiator.class);
+            will(returnValue(new org.gradle.internal.reflect.DirectInstantiator()));
             allowing(parent).get(FileSystem.class);
             will(returnValue(context.mock(FileSystem.class)));
         }});
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
index 19ad21c..4d5365d 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.groovy
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-package org.gradle.api.internal.project;
-
+package org.gradle.api.internal.project
 
 import org.gradle.StartParameter
 import org.gradle.api.internal.classpath.DefaultModuleRegistry
@@ -35,8 +34,8 @@ import org.gradle.internal.service.ServiceRegistry
 import org.gradle.listener.DefaultListenerManager
 import org.gradle.listener.ListenerManager
 import org.gradle.logging.LoggingManagerInternal
-import org.gradle.messaging.concurrent.DefaultExecutorFactory
-import org.gradle.messaging.concurrent.ExecutorFactory
+import org.gradle.internal.concurrent.DefaultExecutorFactory
+import org.gradle.internal.concurrent.ExecutorFactory
 import org.gradle.messaging.remote.MessagingServer
 import org.gradle.process.internal.DefaultWorkerProcessFactory
 import org.gradle.process.internal.WorkerProcessBuilder
@@ -46,43 +45,46 @@ import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.MultiParentClassLoader
 import org.gradle.util.TemporaryFolder
 import org.jmock.integration.junit4.JUnit4Mockery
+import org.gradle.api.internal.*
+import org.gradle.initialization.*
+import org.gradle.internal.reflect.Instantiator
 import org.junit.Rule
+
 import spock.lang.Specification
 import spock.lang.Timeout
-import org.gradle.api.internal.*
-import org.gradle.initialization.*
+
 import static org.hamcrest.Matchers.instanceOf
 import static org.hamcrest.Matchers.sameInstance
 import static org.junit.Assert.assertThat
 
 public class TopLevelBuildServiceRegistryTest extends Specification {
     @Rule
-    public TemporaryFolder tmpDir = new TemporaryFolder();
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final StartParameter startParameter = new StartParameter();
-    private final ServiceRegistry parent = Mock();
-    private final Factory<CacheFactory> cacheFactoryFactory = Mock();
-    private final ClosableCacheFactory cacheFactory = Mock();
-    private final ClassLoaderRegistry classLoaderRegistry = Mock();
+    TemporaryFolder tmpDir = new TemporaryFolder()
+    JUnit4Mockery context = new JUnit4GroovyMockery()
+    StartParameter startParameter = new StartParameter()
+    ServiceRegistry parent = Mock()
+    Factory<CacheFactory> cacheFactoryFactory = Mock()
+    ClosableCacheFactory cacheFactory = Mock()
+    ClassLoaderRegistry classLoaderRegistry = Mock()
 
-    private final TopLevelBuildServiceRegistry registry = new TopLevelBuildServiceRegistry(parent, startParameter);
+    TopLevelBuildServiceRegistry registry = new TopLevelBuildServiceRegistry(parent, startParameter)
 
     def setup() {
         startParameter.gradleUserHomeDir = tmpDir.dir
-        _ * parent.getFactory(CacheFactory.class) >> cacheFactoryFactory
-        _ * cacheFactoryFactory.create() >> cacheFactory
-        _ * parent.get(ClassLoaderRegistry.class) >> classLoaderRegistry
-        _ * parent.getFactory(LoggingManagerInternal) >> Mock(Factory)
-        _ * parent.get(ModuleRegistry) >> new DefaultModuleRegistry()
-        _ * parent.get(PluginModuleRegistry.class) >> Mock(PluginModuleRegistry)
+        parent.getFactory(CacheFactory) >> cacheFactoryFactory
+        cacheFactoryFactory.create() >> cacheFactory
+        parent.get(ClassLoaderRegistry) >> classLoaderRegistry
+        parent.getFactory(LoggingManagerInternal) >> Mock(Factory)
+        parent.get(ModuleRegistry) >> new DefaultModuleRegistry()
+        parent.get(PluginModuleRegistry) >> Mock(PluginModuleRegistry)
     }
 
     def delegatesToParentForUnknownService() {
         setup:
-        parent.get(String.class) >> "value"
+        parent.get(String) >> "value"
 
         expect:
-        registry.get(String.class) == "value"
+        registry.get(String) == "value"
     }
 
     def throwsExceptionForUnknownDomainObject() {
@@ -95,27 +97,27 @@ public class TopLevelBuildServiceRegistryTest extends Specification {
 
     def canCreateServicesForAGradleInstance() {
         setup:
-        GradleInternal gradle = Mock();
-        ServiceRegistryFactory registry = this.registry.createFor(gradle);
+        GradleInternal gradle = Mock()
+        ServiceRegistryFactory registry = this.registry.createFor(gradle)
         expect:
         registry instanceof GradleInternalServiceRegistry
     }
 
     def providesAListenerManager() {
         setup:
-        ListenerManager listenerManager = expectListenerManagerCreated();
+        ListenerManager listenerManager = expectListenerManagerCreated()
         expect:
-        assertThat(registry.get(ListenerManager.class), sameInstance(listenerManager))
+        assertThat(registry.get(ListenerManager), sameInstance(listenerManager))
     }
 
     @Timeout(5)
     def providesAScriptCompilerFactory() {
         setup:
-        expectListenerManagerCreated();
+        expectListenerManagerCreated()
 
         expect:
-        registry.get(ScriptCompilerFactory.class) instanceof DefaultScriptCompilerFactory
-        registry.get(ScriptCompilerFactory.class) == registry.get(ScriptCompilerFactory)
+        registry.get(ScriptCompilerFactory) instanceof DefaultScriptCompilerFactory
+        registry.get(ScriptCompilerFactory) == registry.get(ScriptCompilerFactory)
     }
 
     def providesACacheRepositoryAndCleansUpOnClose() {
@@ -123,117 +125,117 @@ public class TopLevelBuildServiceRegistryTest extends Specification {
         1 * cacheFactory.close()
 
         expect:
-        registry.get(CacheRepository.class) instanceof DefaultCacheRepository
-        registry.get(CacheRepository.class) == registry.get(CacheRepository.class)
-        registry.close();
+        registry.get(CacheRepository) instanceof DefaultCacheRepository
+        registry.get(CacheRepository) == registry.get(CacheRepository)
+        registry.close()
     }
 
     def providesAnInitScriptHandler() {
         setup:
-        allowGetCoreImplClassLoader();
-        expectScriptClassLoaderCreated();
-        expectListenerManagerCreated();
+        allowGetCoreImplClassLoader()
+        expectScriptClassLoaderCreated()
+        expectListenerManagerCreated()
         allowGetGradleDistributionLocator()
 
         expect:
-        registry.get(InitScriptHandler.class) instanceof InitScriptHandler
-        registry.get(InitScriptHandler.class) == registry.get(InitScriptHandler.class)
+        registry.get(InitScriptHandler) instanceof InitScriptHandler
+        registry.get(InitScriptHandler) == registry.get(InitScriptHandler)
     }
 
     def providesAScriptObjectConfigurerFactory() {
         setup:
-        allowGetCoreImplClassLoader();
-        expectListenerManagerCreated();
-        expectScriptClassLoaderCreated();
+        allowGetCoreImplClassLoader()
+        expectListenerManagerCreated()
+        expectScriptClassLoaderCreated()
         expect:
-        assertThat(registry.get(ScriptPluginFactory.class), instanceOf(DefaultScriptPluginFactory.class));
-        assertThat(registry.get(ScriptPluginFactory.class), sameInstance(registry.get(ScriptPluginFactory.class)));
+        assertThat(registry.get(ScriptPluginFactory), instanceOf(DefaultScriptPluginFactory))
+        assertThat(registry.get(ScriptPluginFactory), sameInstance(registry.get(ScriptPluginFactory)))
     }
 
     def providesASettingsProcessor() {
         setup:
-        allowGetCoreImplClassLoader();
-        expectListenerManagerCreated();
-        expectScriptClassLoaderCreated();
+        allowGetCoreImplClassLoader()
+        expectListenerManagerCreated()
+        expectScriptClassLoaderCreated()
         expect:
-        assertThat(registry.get(SettingsProcessor.class), instanceOf(PropertiesLoadingSettingsProcessor.class));
-        assertThat(registry.get(SettingsProcessor.class), sameInstance(registry.get(SettingsProcessor.class)));
+        assertThat(registry.get(SettingsProcessor), instanceOf(PropertiesLoadingSettingsProcessor))
+        assertThat(registry.get(SettingsProcessor), sameInstance(registry.get(SettingsProcessor)))
     }
 
     def providesAnExceptionAnalyser() {
         setup:
-        expectListenerManagerCreated();
+        expectListenerManagerCreated()
         expect:
-        assertThat(registry.get(ExceptionAnalyser.class), instanceOf(DefaultExceptionAnalyser.class));
-        assertThat(registry.get(ExceptionAnalyser.class), sameInstance(registry.get(ExceptionAnalyser.class)));
+        assertThat(registry.get(ExceptionAnalyser), instanceOf(DefaultExceptionAnalyser))
+        assertThat(registry.get(ExceptionAnalyser), sameInstance(registry.get(ExceptionAnalyser)))
     }
 
     def providesAWorkerProcessFactory() {
         setup:
-        expectParentServiceLocated(MessagingServer.class);
-        allowGetCoreImplClassLoader();
+        expectParentServiceLocated(MessagingServer)
+        allowGetCoreImplClassLoader()
 
         expect:
-        assertThat(registry.getFactory(WorkerProcessBuilder.class), instanceOf(DefaultWorkerProcessFactory.class));
+        assertThat(registry.getFactory(WorkerProcessBuilder), instanceOf(DefaultWorkerProcessFactory))
     }
 
     def providesAnIsolatedAntBuilder() {
         setup:
-        expectParentServiceLocated(ClassLoaderFactory.class);
-        allowGetCoreImplClassLoader();
+        expectParentServiceLocated(ClassLoaderFactory)
+        allowGetCoreImplClassLoader()
         expect:
 
-        assertThat(registry.get(IsolatedAntBuilder.class), instanceOf(DefaultIsolatedAntBuilder.class));
-        assertThat(registry.get(IsolatedAntBuilder.class), sameInstance(registry.get(IsolatedAntBuilder.class)));
+        assertThat(registry.get(IsolatedAntBuilder), instanceOf(DefaultIsolatedAntBuilder))
+        assertThat(registry.get(IsolatedAntBuilder), sameInstance(registry.get(IsolatedAntBuilder)))
     }
 
     def providesAProjectFactory() {
         setup:
-        expectParentServiceLocated(Instantiator.class);
-        expectParentServiceLocated(ClassGenerator.class);
+        expectParentServiceLocated(Instantiator)
+        expectParentServiceLocated(ClassGenerator)
         expect:
-        assertThat(registry.get(IProjectFactory.class), instanceOf(ProjectFactory.class));
-        assertThat(registry.get(IProjectFactory.class), sameInstance(registry.get(IProjectFactory.class)));
+        assertThat(registry.get(IProjectFactory), instanceOf(ProjectFactory))
+        assertThat(registry.get(IProjectFactory), sameInstance(registry.get(IProjectFactory)))
     }
 
     def providesAnExecutorFactory() {
         expect:
-        assertThat(registry.get(ExecutorFactory.class), instanceOf(DefaultExecutorFactory.class));
-        assertThat(registry.get(ExecutorFactory.class), sameInstance(registry.get(ExecutorFactory.class)));
+        assertThat(registry.get(ExecutorFactory), instanceOf(DefaultExecutorFactory))
+        assertThat(registry.get(ExecutorFactory), sameInstance(registry.get(ExecutorFactory)))
     }
 
     def providesABuildConfigurer() {
         expect:
-        assertThat(registry.get(BuildConfigurer.class), instanceOf(DefaultBuildConfigurer.class));
-        assertThat(registry.get(BuildConfigurer.class), sameInstance(registry.get(BuildConfigurer.class)));
+        assertThat(registry.get(BuildConfigurer), instanceOf(DefaultBuildConfigurer))
+        assertThat(registry.get(BuildConfigurer), sameInstance(registry.get(BuildConfigurer)))
     }
 
     def providesAPropertiesLoader() {
         expect:
-        assertThat(registry.get(IGradlePropertiesLoader.class), instanceOf(DefaultGradlePropertiesLoader.class));
-        assertThat(registry.get(IGradlePropertiesLoader.class), sameInstance(registry.get(IGradlePropertiesLoader.class)));
+        assertThat(registry.get(IGradlePropertiesLoader), instanceOf(DefaultGradlePropertiesLoader))
+        assertThat(registry.get(IGradlePropertiesLoader), sameInstance(registry.get(IGradlePropertiesLoader)))
     }
 
     def providesABuildLoader() {
         setup:
-        expectParentServiceLocated(Instantiator.class);
+        expectParentServiceLocated(Instantiator)
         expect:
-        assertThat(registry.get(BuildLoader.class), instanceOf(ProjectPropertySettingBuildLoader.class));
-        assertThat(registry.get(BuildLoader.class), sameInstance(registry.get(BuildLoader.class)));
+        assertThat(registry.get(BuildLoader), instanceOf(ProjectPropertySettingBuildLoader))
+        assertThat(registry.get(BuildLoader), sameInstance(registry.get(BuildLoader)))
     }
 
     def providesAProfileEventAdapter() {
         setup:
-        expectParentServiceLocated(BuildRequestMetaData.class);
-        expectListenerManagerCreated();
+        expectParentServiceLocated(BuildRequestMetaData)
+        expectListenerManagerCreated()
 
         expect:
-        assertThat(registry.get(ProfileEventAdapter.class), instanceOf(ProfileEventAdapter.class));
-        assertThat(registry.get(ProfileEventAdapter.class), sameInstance(registry.get(ProfileEventAdapter.class)));
+        assertThat(registry.get(ProfileEventAdapter), instanceOf(ProfileEventAdapter))
+        assertThat(registry.get(ProfileEventAdapter), sameInstance(registry.get(ProfileEventAdapter)))
     }
 
-    private <T> T expectParentServiceLocated(final Class<T> type) {
-        final T t = Mock(type);
+    private <T> T expectParentServiceLocated(Class<T> type) {
+        T t = Mock(type)
         parent.get(type) >> t
         t
     }
@@ -255,10 +257,10 @@ public class TopLevelBuildServiceRegistryTest extends Specification {
     }
 
     private void allowGetGradleDistributionLocator() {
-        parent.get(GradleDistributionLocator.class) >> Mock(GradleDistributionLocator)
+        parent.get(GradleDistributionLocator) >> Mock(GradleDistributionLocator)
     }
 
     public interface ClosableCacheFactory extends CacheFactory {
-        void close();
+        void close()
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
index 47f0ff5..a4c5c26 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
@@ -20,7 +20,6 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.Rule;
 import org.gradle.api.Task;
 import org.gradle.api.UnknownTaskException;
-import org.gradle.api.internal.Instantiator;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
@@ -45,7 +44,7 @@ public class DefaultTaskContainerTest {
     private final ITaskFactory taskFactory = context.mock(ITaskFactory.class);
     private final ProjectInternal project = context.mock(ProjectInternal.class, "<project>");
     private int taskCount;
-    private final DefaultTaskContainer container = new DefaultTaskContainer(project, context.mock(Instantiator.class), taskFactory);
+    private final DefaultTaskContainer container = new DefaultTaskContainer(project, context.mock(org.gradle.internal.reflect.Instantiator.class), taskFactory);
 
     @Test
     public void addsTaskWithMap() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy
index 1a7146b..dbb2569 100755
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy
@@ -1,111 +1,111 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal.tasks.util
-
-import static org.gradle.util.Matchers.*
-
-import org.gradle.api.internal.file.FileResolver
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.process.ProcessForkOptions
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertThat
-import org.gradle.process.internal.DefaultProcessForkOptions
-import org.junit.Before
-import org.gradle.api.internal.file.FileSource
-
- at RunWith(JMock.class)
-public class DefaultProcessForkOptionsTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final FileResolver resolver = context.mock(FileResolver.class)
-    private final FileSource workingDir = context.mock(FileSource.class)
-    private DefaultProcessForkOptions options
-    private final File baseDir = new File("base-dir")
-
-    @Before
-    public void setup() {
-        context.checking {
-            allowing(resolver).resolveLater(".")
-            will(returnValue(workingDir))
-        }
-        options = new DefaultProcessForkOptions(resolver)
-    }
-
-    @Test
-    public void defaultValues() {
-        assertThat(options.executable, nullValue())
-        assertThat(options.environment, not(isEmptyMap()))
-    }
-
-    @Test
-    public void resolvesWorkingDirectoryOnGet() {
-        context.checking {
-            one(resolver).resolveLater(12)
-            will(returnValue(workingDir))
-        }
-
-        options.workingDir = 12
-
-        context.checking {
-            one(workingDir).get()
-            will(returnValue(baseDir))
-        }
-
-        assertThat(options.workingDir, equalTo(baseDir))
-    }
-
-    @Test
-    public void convertsEnvironmentToString() {
-        options.environment = [key1: 12, key2: "${1+2}"]
-
-        assertThat(options.actualEnvironment, equalTo(key1: '12', key2: '3'))
-    }
-
-    @Test
-    public void canAddEnvironmentVariables() {
-        options.environment = [:]
-
-        assertThat(options.environment, equalTo([:]))
-
-        options.environment('key', 12)
-
-        assertThat(options.environment, equalTo([key: 12]))
-        assertThat(options.actualEnvironment, equalTo([key: '12']))
-
-        options.environment(key2: "value")
-
-        assertThat(options.environment, equalTo([key: 12, key2: "value"]))
-    }
-    
-    @Test
-    public void canCopyToTargetOptions() {
-        options.executable('executable')
-        options.environment('key', 12)
-
-        ProcessForkOptions target = context.mock(ProcessForkOptions.class)
-        context.checking {
-            one(target).setExecutable('executable')
-            one(target).setWorkingDir(workingDir)
-            one(target).setEnvironment(withParam(not(isEmptyMap())))
-        }
-
-        options.copyTo(target)
-    }
-}
-
-
+/*
+ * Copyright 2010 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 org.gradle.api.internal.tasks.util
+
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.internal.Factory
+import org.gradle.process.ProcessForkOptions
+import org.gradle.process.internal.DefaultProcessForkOptions
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import static org.gradle.util.Matchers.isEmptyMap
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+
+ at RunWith(JMock.class)
+public class DefaultProcessForkOptionsTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final FileResolver resolver = context.mock(FileResolver.class)
+    private final Factory workingDir = context.mock(Factory.class)
+    private DefaultProcessForkOptions options
+    private final File baseDir = new File("base-dir")
+
+    @Before
+    public void setup() {
+        context.checking {
+            allowing(resolver).resolveLater(".")
+            will(returnValue(workingDir))
+        }
+        options = new DefaultProcessForkOptions(resolver)
+    }
+
+    @Test
+    public void defaultValues() {
+        assertThat(options.executable, nullValue())
+        assertThat(options.environment, not(isEmptyMap()))
+    }
+
+    @Test
+    public void resolvesWorkingDirectoryOnGet() {
+        context.checking {
+            one(resolver).resolveLater(12)
+            will(returnValue(workingDir))
+        }
+
+        options.workingDir = 12
+
+        context.checking {
+            one(workingDir).create()
+            will(returnValue(baseDir))
+        }
+
+        assertThat(options.workingDir, equalTo(baseDir))
+    }
+
+    @Test
+    public void convertsEnvironmentToString() {
+        options.environment = [key1: 12, key2: "${1+2}"]
+
+        assertThat(options.actualEnvironment, equalTo(key1: '12', key2: '3'))
+    }
+
+    @Test
+    public void canAddEnvironmentVariables() {
+        options.environment = [:]
+
+        assertThat(options.environment, equalTo([:]))
+
+        options.environment('key', 12)
+
+        assertThat(options.environment, equalTo([key: 12]))
+        assertThat(options.actualEnvironment, equalTo([key: '12']))
+
+        options.environment(key2: "value")
+
+        assertThat(options.environment, equalTo([key: 12, key2: "value"]))
+    }
+    
+    @Test
+    public void canCopyToTargetOptions() {
+        options.executable('executable')
+        options.environment('key', 12)
+
+        ProcessForkOptions target = context.mock(ProcessForkOptions.class)
+        context.checking {
+            one(target).setExecutable('executable')
+            one(target).setWorkingDir(workingDir)
+            one(target).setEnvironment(withParam(not(isEmptyMap())))
+        }
+
+        options.copyTo(target)
+    }
+}
+
+
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/plugins/ExtraPropertiesExtensionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/plugins/ExtraPropertiesExtensionTest.groovy
index 3eece02..db0f01f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/plugins/ExtraPropertiesExtensionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/plugins/ExtraPropertiesExtensionTest.groovy
@@ -176,6 +176,7 @@ abstract class ExtraPropertiesExtensionTest<T extends ExtraPropertiesExtension>
 
         project.task("custom").extensions.add("dynamic", extension)
 
+        when:
         project.custom {
             dynamic {
                 doLast { // should resolve to task.doLast
@@ -184,7 +185,7 @@ abstract class ExtraPropertiesExtensionTest<T extends ExtraPropertiesExtension>
             }
         }
 
-        expect:
+        then:
         notThrown(Exception)
     }
     
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy
new file mode 100644
index 0000000..5609e9a
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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 org.gradle.api.specs
+
+import spock.lang.Specification
+import spock.lang.Issue
+
+class SpecsTest extends Specification {
+
+    def "can convert closures to specs"() {
+        given:
+        def spec = Specs.convertClosureToSpec {
+            it > 10
+        }
+
+        expect:
+        !spec.isSatisfiedBy(5)
+        spec.isSatisfiedBy(15)
+    }
+
+    @Issue("GRADLE-2288")
+    def "closure specs use groovy truth"() {
+        def spec = Specs.convertClosureToSpec {
+            it
+        }
+
+        expect:
+        !spec.isSatisfiedBy("")
+        spec.isSatisfiedBy([1,2,3])
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
index 48c4435..bb3d948 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/ProjectReportTaskTest.groovy
@@ -17,7 +17,7 @@ package org.gradle.api.tasks.diagnostics
 
 import org.gradle.api.Project
 import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.logging.internal.TestStyledTextOutput
+import org.gradle.logging.TestStyledTextOutput
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.groovy
index 674325a..0a08875 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/AsciiReportRendererTest.groovy
@@ -17,7 +17,7 @@ package org.gradle.api.tasks.diagnostics.internal
 
 import org.gradle.api.Project
 import org.gradle.api.artifacts.Configuration
-import org.gradle.logging.internal.TestStyledTextOutput
+import org.gradle.logging.TestStyledTextOutput
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 import org.gradle.api.artifacts.ResolvedConfiguration
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
index 7a2cb2c..06e03c1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/PropertyReportRendererTest.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.tasks.diagnostics.internal;
 
-import org.gradle.logging.internal.TestStyledTextOutput;
+import org.gradle.logging.TestStyledTextOutput;
 import org.junit.Test;
 
 import static org.gradle.util.Matchers.containsLine;
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy
index 9e46523..bf01b11 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TaskReportRendererTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.tasks.diagnostics.internal
 
 import org.gradle.api.Rule
-import org.gradle.logging.internal.TestStyledTextOutput
+import org.gradle.logging.TestStyledTextOutput
 
 /**
  * @author Hans Dockter
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
index 34c597b..c81142b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/TextReportRendererTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.tasks.diagnostics.internal;
 
 import org.gradle.api.Project
 import org.gradle.logging.internal.StreamingStyledTextOutput
-import org.gradle.logging.internal.TestStyledTextOutput
+import org.gradle.logging.TestStyledTextOutput
 import org.gradle.util.TemporaryFolder
 import org.jmock.Expectations
 import org.jmock.integration.junit4.JMock
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
index cdb20a4..06367b0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheAccessTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.cache.internal
 
 import org.gradle.internal.Factory
-import org.gradle.cache.Serializer
+import org.gradle.messaging.serialize.Serializer
 import org.gradle.cache.internal.btree.BTreePersistentIndexedCache
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
@@ -139,10 +139,10 @@ class DefaultCacheAccessTest extends Specification {
             cache.get("key")
         }
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFromFile(_)
+        _ * lock.readFile(_)
 
         and:
-        _ * lock.writeToFile(_)
+        _ * lock.writeFile(_)
         1 * lock.close()
         0 * _._
     }
@@ -162,8 +162,8 @@ class DefaultCacheAccessTest extends Specification {
         1 * action.create() >> {
             cache.get("key")
         }
-        _ * lock.readFromFile(_)
-        _ * lock.writeToFile(_)
+        _ * lock.readFile(_)
+        _ * lock.writeFile(_)
 
         and:
         0 * _._
@@ -188,8 +188,8 @@ class DefaultCacheAccessTest extends Specification {
         }
         1 * longRunningAction.create()
         2 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFromFile(_)
-        _ * lock.writeToFile(_)
+        _ * lock.readFile(_)
+        _ * lock.writeFile(_)
         2 * lock.close()
         0 * _._
     }
@@ -256,8 +256,8 @@ class DefaultCacheAccessTest extends Specification {
         }
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "nested 2") >> lock
-        _ * lock.readFromFile(_)
-        _ * lock.writeToFile(_)
+        _ * lock.readFile(_)
+        _ * lock.writeFile(_)
         2 * lock.close()
         0 * _._
     }
@@ -284,8 +284,8 @@ class DefaultCacheAccessTest extends Specification {
         }
         1 * nestedAction.create()
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFromFile(_)
-        _ * lock.writeToFile(_)
+        _ * lock.readFile(_)
+        _ * lock.writeFile(_)
         1 * lock.close()
         0 * _._
     }
@@ -310,8 +310,8 @@ class DefaultCacheAccessTest extends Specification {
             cache.get("key")
         }
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFromFile(_)
-        _ * lock.writeToFile(_)
+        _ * lock.readFile(_)
+        _ * lock.writeFile(_)
         1 * lock.close()
         0 * _._
     }
@@ -331,10 +331,10 @@ class DefaultCacheAccessTest extends Specification {
             cache.get("key")
         }
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>", "some operation") >> lock
-        _ * lock.readFromFile(_)
+        _ * lock.readFile(_)
 
         and:
-        _ * lock.writeToFile(_) >> {Runnable runnable -> runnable.run()}
+        _ * lock.writeFile(_) >> {Runnable runnable -> runnable.run()}
         1 * backingCache.close()
         1 * lock.close()
         0 * _._
@@ -343,8 +343,8 @@ class DefaultCacheAccessTest extends Specification {
     def "closes caches on close when initial lock mode is not none"() {
         given:
         1 * lockManager.lock(lockFile, Exclusive, "<display-name>") >> lock
-        _ * lock.readFromFile(_) >> {Factory factory -> factory.create()}
-        _ * lock.writeToFile(_) >> {Runnable runnable -> runnable.run()}
+        _ * lock.readFile(_) >> {Factory factory -> factory.create()}
+        _ * lock.writeFile(_) >> {Runnable runnable -> runnable.run()}
 
         and:
         manager.open(Exclusive)
@@ -355,8 +355,8 @@ class DefaultCacheAccessTest extends Specification {
         manager.close()
 
         then:
-        _ * lock.readFromFile(_) >> {Factory factory -> factory.create()}
-        _ * lock.writeToFile(_) >> {Runnable runnable -> runnable.run()}
+        _ * lock.readFile(_) >> {Factory factory -> factory.create()}
+        _ * lock.writeFile(_) >> {Runnable runnable -> runnable.run()}
         1 * backingCache.close()
         1 * lock.close()
         0 * _._
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
index 3db8098..2d6948a 100755
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultCacheFactoryTest.groovy
@@ -84,6 +84,23 @@ class DefaultCacheFactoryTest extends Specification {
         1 * closed.execute(cache)
     }
 
+    public void "creates DelegateOnDemandPersistentDirectoryCache cache instance for LockMode.NONE"() {
+            when:
+            def factory = factoryFactory.create()
+            def cache = factory.open(tmpDir.dir, "<display>", CacheUsage.ON, null, [prop: 'value'], FileLockManager.LockMode.None, null)
+
+            then:
+            cache instanceof DelegateOnDemandPersistentDirectoryCache
+            cache.baseDir == tmpDir.dir
+            cache.toString().startsWith "Delegate On Demand Cache for <display>"
+
+            when:
+            factory.close()
+
+            then:
+            1 * closed.execute(cache)
+        }
+
     public void "creates indexed cache instance"() {
         when:
         def factory = factoryFactory.create()
@@ -391,9 +408,10 @@ class DefaultCacheFactoryTest extends Specification {
         CacheValidator validator = Mock()
 
         when:
-        def cache = factory1.open(tmpDir.dir, null, CacheUsage.ON, validator, [prop: 'value'], FileLockManager.LockMode.Exclusive, null)
+        def cache = factory1.open(tmpDir.dir, null, CacheUsage.ON, validator, [prop: 'value'], FileLockManager.LockMode.Shared, null)
 
         then:
+        validator.isValid() >>> [false, true]
         cache != null
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
index 7384ca0..66bb064 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultFileLockManagerTest.groovy
@@ -16,35 +16,80 @@
 
 package org.gradle.cache.internal
 
-import org.gradle.internal.Factory
+import org.apache.commons.lang.RandomStringUtils
 import org.gradle.cache.internal.FileLockManager.LockMode
+import org.gradle.internal.Factory
+import org.gradle.util.Requires
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
-import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
-
 import org.junit.Rule
 import spock.lang.Specification
+import spock.lang.Unroll
+
+import static org.gradle.cache.internal.FileLockManager.LockMode.Exclusive
+import static org.gradle.cache.internal.FileLockManager.LockMode.Shared
 
 /**
  * @author: Szczepan Faber, created at: 8/30/11
  */
 class DefaultFileLockManagerTest extends Specification {
     @Rule TemporaryFolder tmpDir = new TemporaryFolder()
-    ProcessMetaDataProvider metaDataProvider = Mock()
+    def metaDataProvider = Mock(ProcessMetaDataProvider)
     FileLockManager manager = new DefaultFileLockManager(metaDataProvider)
 
+    TestFile testFile
+    TestFile testFileLock
+    TestFile testDir
+    TestFile testDirLock
+
     def setup() {
+        testFile = tmpDir.createFile("state.bin")
+        testFileLock = tmpDir.file(testFile.name + ".lock")
+        testDir = tmpDir.createDir("lockable-dir")
+        testDirLock = tmpDir.file("${testDir.name}/${testDir.name}.lock")
+
         metaDataProvider.processIdentifier >> '123'
         metaDataProvider.processDisplayName >> 'process'
     }
 
+    @Unroll
+    "#operation throws integrity exception when not cleanly unlocked file"() {
+        given:
+        unlockUncleanly()
+
+        and:
+        def lock = createLock()
+
+        when:
+        lock."$operation"(arg)
+
+        then:
+        thrown FileIntegrityViolationException
+
+        where:
+        operation    | arg
+        "readFile"   | {} as Factory
+        "updateFile" | {} as Runnable
+    }
+
+    def "writeFile does not throw integrity exception when not cleanly unlocked file"() {
+        given:
+        unlockUncleanly()
+
+        when:
+        createLock(Exclusive).writeFile { }
+
+        then:
+        notThrown FileIntegrityViolationException
+    }
+
     def "can lock a file"() {
         when:
-        def lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Shared, "lock")
+        def lock = createLock()
 
         then:
-        lock.isLockFile(tmpDir.createFile("file.txt.lock"))
+        lock.isLockFile(tmpDir.createFile(testFile.name + ".lock"))
 
         cleanup:
         lock?.close()
@@ -52,10 +97,10 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "can lock a directory"() {
         when:
-        def lock = manager.lock(tmpDir.createDir("some-dir"), LockMode.Shared, "lock")
+        def lock = createLock(testDir)
 
         then:
-        lock.isLockFile(tmpDir.createFile("some-dir/some-dir.lock"))
+        lock.isLockFile(testDirLock)
 
         cleanup:
         lock?.close()
@@ -63,11 +108,11 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "can lock a file once it has been closed"() {
         given:
-        def fileLock = lock(FileLockManager.LockMode.Exclusive);
+        def fileLock = createLock(Exclusive)
         fileLock.close()
 
         when:
-        lock(FileLockManager.LockMode.Exclusive);
+        createLock(Exclusive)
 
         then:
         notThrown(RuntimeException)
@@ -75,7 +120,7 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "lock on new file is not unlocked cleanly"() {
         when:
-        def lock = manager.lock(tmpDir.createFile("file.txt"), mode, "lock")
+        def lock = createLock(mode)
 
         then:
         !lock.unlockedCleanly
@@ -84,20 +129,67 @@ class DefaultFileLockManagerTest extends Specification {
         lock?.close()
 
         where:
-        mode << [LockMode.Shared, LockMode.Exclusive]
+        mode << [Shared, Exclusive]
+    }
+
+    def "file is 'invalid' unless a valid lock file exists"() {
+        given:
+        def lock = createLock(Exclusive)
+
+        when:
+        lock.readFile({})
+
+        then:
+        thrown FileIntegrityViolationException
+
+        when:
+        lock.updateFile({})
+
+        then:
+        thrown FileIntegrityViolationException
+
+        when:
+        lock.writeFile({})
+
+        and:
+        lock.readFile({})
+        lock.updateFile({})
+
+        then:
+        notThrown FileIntegrityViolationException
+    }
+
+    def "integrity violation exception is thrown after a failed write"() {
+        given:
+        def e = new RuntimeException()
+        def lock = createLock()
+
+        when:
+        lock.writeFile {}
+        lock.updateFile { throw e }
+
+        then:
+        thrown RuntimeException
+
+        when:
+        lock.readFile({})
+
+        then:
+        thrown FileIntegrityViolationException
     }
 
     def "existing lock is unlocked cleanly after writeToFile() has been called"() {
         when:
-        def lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Exclusive, "lock")
-        lock.writeToFile({} as Runnable)
+        def lock = this.createLock(Exclusive)
+        lock.writeFile({})
+        lock.updateFile({} as Runnable)
 
         then:
         lock.unlockedCleanly
 
         when:
         lock.close()
-        lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Exclusive, "lock")
+        lock = createLock(Exclusive)
 
         then:
         lock.unlockedCleanly
@@ -110,8 +202,9 @@ class DefaultFileLockManagerTest extends Specification {
         def failure = new RuntimeException()
 
         when:
-        def lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Exclusive, "lock")
-        lock.writeToFile({throw failure} as Runnable)
+        def lock = createLock(Exclusive)
+        lock.writeFile({ })
+        lock.updateFile({throw failure} as Runnable)
 
         then:
         RuntimeException e = thrown()
@@ -120,7 +213,7 @@ class DefaultFileLockManagerTest extends Specification {
 
         when:
         lock.close()
-        lock = manager.lock(tmpDir.createFile("file.txt"), LockMode.Exclusive, "lock")
+        lock = createLock(Exclusive)
 
         then:
         !lock.unlockedCleanly
@@ -131,10 +224,10 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "cannot lock a file twice in single process"() {
         given:
-        lock(LockMode.Exclusive);
+        createLock(Exclusive);
 
         when:
-        lock(LockMode.Exclusive);
+        createLock(Exclusive);
 
         then:
         thrown(IllegalStateException)
@@ -142,10 +235,10 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "cannot lock twice in single process for mixed modes"() {
         given:
-        lock(LockMode.Exclusive);
+        createLock(Exclusive);
 
         when:
-        lock(LockMode.Shared);
+        createLock(Shared);
 
         then:
         thrown(IllegalStateException)
@@ -153,10 +246,10 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "cannot lock twice in single process for shared mode"() {
         given:
-        lock(LockMode.Shared);
+        createLock(Shared);
 
         when:
-        lock(LockMode.Shared);
+        createLock(Shared);
 
         then:
         thrown(IllegalStateException)
@@ -164,7 +257,7 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "can close a lock multiple times"() {
         given:
-        def lock = lock(LockMode.Exclusive)
+        def lock = createLock(Exclusive)
         lock.close()
 
         expect:
@@ -173,11 +266,11 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "cannot read from file after lock has been closed"() {
         given:
-        def lock = lock(LockMode.Exclusive)
+        def lock = createLock(Exclusive)
         lock.close()
 
         when:
-        lock.readFromFile({} as Factory)
+        lock.readFile({} as Factory)
 
         then:
         thrown(IllegalStateException)
@@ -185,132 +278,158 @@ class DefaultFileLockManagerTest extends Specification {
 
     def "cannot write to file after lock has been closed"() {
         given:
-        def lock = lock(LockMode.Exclusive)
+        def lock = createLock(Exclusive)
         lock.close()
 
         when:
-        lock.writeToFile({} as Runnable)
+        lock.updateFile({} as Runnable)
 
         then:
         thrown(IllegalStateException)
     }
 
     def "leaves version 1 lock file after exclusive lock on new file closed"() {
-        def file = tmpDir.file("state.bin")
-        def lockFile = tmpDir.file("state.bin.lock")
-
         when:
-        def lock = manager.lock(file, LockMode.Exclusive, "foo")
+        def lock = createLock(Exclusive)
         lock.close()
 
         then:
-        lock.isLockFile(lockFile)
+        lock.isLockFile(testFileLock)
 
         and:
-        isVersion1LockFile(lockFile)
+        isVersion1LockFile(testFileLock)
     }
 
     def "leaves empty lock file after shared lock on new file closed"() {
-        def file = tmpDir.file("state.bin")
-        def lockFile = tmpDir.file("state.bin.lock")
-
         when:
-        def lock = manager.lock(file, LockMode.Shared, "foo")
+        def lock = createLock()
         lock.close()
 
         then:
-        lock.isLockFile(lockFile)
+        lock.isLockFile(testFileLock)
 
         and:
-        isEmptyLockFile(lockFile)
+        isEmptyLockFile(testFileLock)
     }
 
     def "leaves version 1 lock file after lock on existing file is closed"() {
-        def file = tmpDir.file("state.bin")
-        def lockFile = tmpDir.file("state.bin.lock")
-        lockFile.withDataOutputStream {
+        testFileLock.withDataOutputStream {
             it.writeByte(1)
             it.writeBoolean(false)
         }
 
         when:
-        def lock = manager.lock(file, mode, "foo")
+        def lock = createLock(mode)
         lock.close()
 
         then:
-        lock.isLockFile(lockFile)
+        lock.isLockFile(testFileLock)
 
         and:
-        isVersion1LockFile(lockFile)
+        isVersion1LockFile(testFileLock)
 
         where:
-        mode << [LockMode.Shared, LockMode.Exclusive]
+        mode << [Shared, Exclusive]
     }
 
     @Requires(TestPrecondition.NO_FILE_LOCK_ON_OPEN)
     def "writes version 2 lock file while exclusive lock is open"() {
-        def file = tmpDir.file("state.bin")
-        def lockFile = tmpDir.file("state.bin.lock")
-
         when:
-        def lock = manager.lock(file, LockMode.Exclusive, "foo", "operation")
+        def lock = createLock(Exclusive)
 
         then:
-        lock.isLockFile(lockFile)
+        lock.isLockFile(testFileLock)
 
         and:
-        isVersion2LockFile(lockFile)
+        isVersion2LockFile(testFileLock)
 
         cleanup:
         lock?.close()
     }
 
     def "can acquire lock on partially written lock file"() {
-        def file = tmpDir.file("state.bin")
-        def lockFile = tmpDir.file("state.bin.lock")
-
         when:
-        lockFile.withDataOutputStream {
+        testFileLock.withDataOutputStream {
             it.writeByte(1)
         }
-        def lock = manager.lock(file, mode, "foo")
+        def lock = createLock(mode)
 
         then:
-        lock.isLockFile(lockFile)
+        lock.isLockFile(testFileLock)
         lock.close()
 
         when:
-        lockFile.withDataOutputStream {
+        testFileLock.withDataOutputStream {
             it.writeByte(1)
             it.writeBoolean(true)
             it.writeByte(2)
             it.writeByte(12)
         }
-        lock = manager.lock(file, mode, "foo")
+        lock = createLock(mode)
 
         then:
-        lock.isLockFile(lockFile)
+        lock.isLockFile(testFileLock)
         lock.close()
 
         where:
-        mode << [LockMode.Shared, LockMode.Exclusive]
+        mode << [Shared, Exclusive]
     }
 
     def "fails to acquire lock on lock file with unknown version"() {
-        def file = tmpDir.file("state.bin")
-        def lockFile = tmpDir.file("state.bin.lock")
-        lockFile.withDataOutputStream {
+        testFileLock.withDataOutputStream {
             it.writeByte(125)
         }
 
         when:
-        manager.lock(file, mode, "foo")
+        createLock(mode)
 
         then:
         thrown(IllegalStateException)
 
         where:
-        mode << [LockMode.Shared, LockMode.Exclusive]
+        mode << [Shared, Exclusive]
+    }
+
+    @Requires(TestPrecondition.NO_FILE_LOCK_ON_OPEN)
+    def "long descriptor strings are trimmed when written to information region"() {
+        setup:
+        def customMetaDataProvider = Mock(ProcessMetaDataProvider)
+        def processIdentifier = RandomStringUtils.randomAlphanumeric(1000)
+        1 * customMetaDataProvider.processIdentifier >> processIdentifier
+        def customManager = new DefaultFileLockManager(customMetaDataProvider)
+        def operationalDisplayName = RandomStringUtils.randomAlphanumeric(1000)
+
+        when:
+        customManager.lock(testFile, Exclusive, "targetDisplayName", operationalDisplayName)
+
+        then:
+        isVersion2LockFile(testFileLock, processIdentifier.substring(0, DefaultFileLockManager.INFORMATION_REGION_DESCR_CHUNK_LIMIT), operationalDisplayName.substring(0, DefaultFileLockManager.INFORMATION_REGION_DESCR_CHUNK_LIMIT))
+    }
+
+    def "require exclusive lock for writing"() {
+        given:
+        def lock = createLock(Shared)
+
+        when:
+        lock.writeFile {}
+
+        then:
+        thrown InsufficientLockModeException
+    }
+
+    def "require exclusive lock for updating"() {
+        given:
+        def writeLock = createLock(Exclusive)
+        writeLock.writeFile {}
+        writeLock.close()
+
+        def lock = createLock(Shared)
+
+        when:
+        lock.updateFile {}
+
+        then:
+        thrown InsufficientLockModeException
     }
 
     private void isEmptyLockFile(TestFile lockFile) {
@@ -327,20 +446,29 @@ class DefaultFileLockManagerTest extends Specification {
         }
     }
 
-    private void isVersion2LockFile(TestFile lockFile) {
+    private void isVersion2LockFile(TestFile lockFile, String processIdentifier = "123", String operationalName = 'operation') {
         assert lockFile.isFile()
         assert lockFile.length() > 3
+        assert lockFile.length() <= 2048
         lockFile.withDataInputStream { str ->
             assert str.readByte() == 1
             assert !str.readBoolean()
             assert str.readByte() == 2
-            assert str.readUTF() == '123'
-            assert str.readUTF() == 'operation'
+            assert str.readUTF() == processIdentifier
+            assert str.readUTF() == operationalName
             assert str.read() < 0
         }
     }
 
-    private FileLock lock(LockMode lockMode) {
-        return manager.lock(tmpDir.file("state.bin"), lockMode, "foo")
+    private FileLock createLock(File testFile) {
+        createLock(Shared, testFile)
+    }
+
+    private FileLock createLock(LockMode lockMode = Shared, File file = testFile) {
+        manager.lock(file, lockMode, "foo", "operation")
+    }
+
+    private File unlockUncleanly(LockMode lockMode = Shared, File file = testFile) {
+        DefaultFileLockManagerTestHelper.unlockUncleanly(file)
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy
new file mode 100644
index 0000000..40a473d
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheSpec.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012 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 org.gradle.cache.internal
+
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+import org.gradle.CacheUsage
+import org.gradle.cache.CacheValidator
+import org.gradle.api.Action
+import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.*
+
+class DefaultPersistentDirectoryCacheSpec extends Specification {
+
+    @Rule TemporaryFolder tmp
+    
+    def "will rebuild cache if not unlocked cleanly"() {
+        given:
+        def dir = tmp.createDir("cache")
+        def initd = false
+        def init = { initd = true } as Action
+        unlockUncleanly(new File(dir, "cache.properties"))
+        def cache = new DefaultPersistentDirectoryCache(
+                dir, "test", CacheUsage.ON, { true } as CacheValidator, [:], FileLockManager.LockMode.Exclusive, init, createDefaultFileLockManager()
+        )
+        
+        when:
+        cache.open()
+
+        then:
+        initd
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
index c20d7c7..ae5a442 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DefaultPersistentDirectoryCacheTest.java
@@ -32,12 +32,14 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
 import static org.gradle.cache.internal.FileLockManager.LockMode;
 import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
@@ -123,8 +125,11 @@ public class DefaultPersistentDirectoryCacheTest {
 
         context.checking(new Expectations() {{
             one(action).execute(with(notNullValue(PersistentCache.class)));
-            one(invalidator).isValid();
+            exactly(2).of(invalidator).isValid();
             will(returnValue(false));
+            allowing(invalidator).isValid();
+            will(returnValue(true));
+
         }});
 
         DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, invalidator, properties, LockMode.Shared, action, lockManager);
@@ -133,6 +138,29 @@ public class DefaultPersistentDirectoryCacheTest {
     }
 
     @Test
+    public void exceptionThrownIfValidCacheCannotBeInitd() {
+        TestFile dir = createCacheDir();
+
+        context.checking(new Expectations() {{
+            allowing(action).execute(with(notNullValue(PersistentCache.class)));
+        }});
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, "<display-name>", CacheUsage.ON, null, properties, LockMode.Shared, action, lockManager) {
+            @Override
+            protected boolean determineIfCacheIsValid(FileLock lock) throws IOException {
+                return false;
+            }
+        };
+
+        try {
+            cache.open();
+            fail("expected exception");
+        } catch (CacheOpenException e) {
+            assertNotNull(e); // to make block not empty
+        }
+    }
+
+    @Test
     public void rebuildsCacheWhenInitialiserFailedOnPreviousOpen() {
         TestFile dir = tmpDir.getDir().file("dir").createDir();
         final RuntimeException failure = new RuntimeException();
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy
new file mode 100644
index 0000000..8e34194
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/DelegateOnDemandPersistentDirectoryCacheSpec.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2012 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 org.gradle.cache.internal
+
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+import spock.lang.Unroll
+import org.gradle.internal.Factory
+import org.gradle.cache.CacheOpenException
+
+class DelegateOnDemandPersistentDirectoryCacheSpec extends Specification {
+
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+    DefaultPersistentDirectoryCache delegateCache = Mock()
+    File dir = tmpDir.createDir("cacheDir")
+    String displayName = "test display name"
+    FileLockManager.LockMode lockMode = FileLockManager.LockMode.None
+    FileLockManager lockManager = Mock()
+
+
+    @Unroll
+    def "#methodName opens delegate, executes #methodName on delegate and closes delegate"() {
+        given:
+        Runnable runnable = {} as Runnable
+        DelegateOnDemandPersistentDirectoryCache cut = new DelegateOnDemandPersistentDirectoryCache(delegateCache)
+        when:
+        cut.open()
+        and:
+        cut."${methodName}"("operation name", runnable)
+        then:
+        1 * delegateCache.open()
+        1 * delegateCache."${methodName}"("operation name", runnable) //checking against *params fails here. TODO: ping Peter for details
+        1 * delegateCache.close()
+        where:
+        methodName << ["useCache", "longRunningOperation"]
+    }
+
+    @Unroll
+    def "#methodName opens delegate, executes #methodName on delegate, closes delegate and returns delegate return value of delegates #methodName"() {
+        given:
+        org.gradle.internal.Factory factory = {-> "testResult" } as Factory
+        DelegateOnDemandPersistentDirectoryCache cut = new DelegateOnDemandPersistentDirectoryCache(delegateCache)
+
+        when:
+        cut.open()
+        and:
+        cut."${methodName}"("operation name", factory)
+
+        then:
+        1 * delegateCache.open()
+        1 * delegateCache."${methodName}"("operation name", factory) >> "testResult"
+        1 * delegateCache.close()
+
+        where:
+        methodName << ["useCache", "longRunningOperation"]
+    }
+
+    @Unroll
+    def "#methodName with #actionType only allowed on explicit opened cache"() {
+        given:
+        DelegateOnDemandPersistentDirectoryCache cut = new DelegateOnDemandPersistentDirectoryCache(delegateCache)
+        when:
+        cut."${methodName}"(* methodParameters)
+        then:
+        thrown(CacheOpenException)
+        0 * delegateCache.open()
+        0 * delegateCache."${methodName}"(* _)
+        0 * delegateCache.close()
+        where:
+        methodName             | methodParameters                                                                       | actionType // for test naming only
+        "useCache"             | ["operation name", {-> "factory return "} as org.gradle.internal.Factory]              | "Factory"
+        "useCache"             | ["operation name", {} as Runnable]                                                     | "Runnable"
+        "longRunningOperation" | ["long running operation name", {-> "factory return "} as org.gradle.internal.Factory] | "Factory"
+        "longRunningOperation" | ["long running operation name", {} as Runnable]                                        | "Runnable"
+    }
+
+    @Unroll
+    def "#methodName calls delegated close"() {
+        given:
+        DelegateOnDemandPersistentDirectoryCache cut = new DelegateOnDemandPersistentDirectoryCache(delegateCache)
+        when:
+        cut."${methodName}"()
+        then:
+        1 * delegateCache."${methodName}"()
+        where:
+        methodName << ['close', 'getLock']
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy
index 4e8c7a5..f69d3d0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/MultiProcessSafePersistentIndexedCacheTest.groovy
@@ -30,7 +30,7 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         cache.get("value")
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * fileAccess.writeFile(!null) >> { Runnable action -> action.run() }
         1 * factory.create() >> backingCache
     }
 
@@ -45,7 +45,7 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         result == "result"
 
         and:
-        1 * fileAccess.readFromFile(!null) >> { Factory action -> action.create() }
+        1 * fileAccess.readFile(!null) >> { Factory action -> action.create() }
         1 * backingCache.get("value") >> "result"
         0 * _._
     }
@@ -58,7 +58,7 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         cache.put("key", "value")
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * fileAccess.writeFile(!null) >> { Runnable action -> action.run() }
         1 * backingCache.put("key", "value")
         0 * _._
     }
@@ -71,7 +71,7 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         cache.remove("key")
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * fileAccess.writeFile(!null) >> { Runnable action -> action.run() }
         1 * backingCache.remove("key")
         0 * _._
     }
@@ -84,7 +84,7 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         cache.close()
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * fileAccess.writeFile(!null) >> { Runnable action -> action.run() }
         1 * backingCache.close()
         0 * _._
     }
@@ -97,7 +97,7 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         cache.onEndWork()
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * fileAccess.writeFile(!null) >> { Runnable action -> action.run() }
         1 * backingCache.close()
         0 * _._
     }
@@ -125,7 +125,7 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
         cache.close()
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * fileAccess.writeFile(!null) >> { Runnable action -> action.run() }
         1 * backingCache.close()
         0 * _._
 
@@ -137,7 +137,7 @@ class MultiProcessSafePersistentIndexedCacheTest extends Specification {
     }
 
     def cacheOpened() {
-        1 * fileAccess.writeToFile(!null) >> { Runnable action -> action.run() }
+        1 * fileAccess.writeFile(!null) >> { Runnable action -> action.run() }
         1 * factory.create() >> backingCache
         
         cache.get("something")
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
index 33ffcb2..ba26dce 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/OnDemandFileAccessTest.groovy
@@ -15,39 +15,101 @@
  */
 package org.gradle.cache.internal
 
-import org.gradle.internal.Factory
 import org.gradle.cache.internal.FileLockManager.LockMode
+import org.gradle.internal.Factory
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
 import spock.lang.Specification
 
 class OnDemandFileAccessTest extends Specification {
     final FileLockManager manager = Mock()
     final FileLock targetLock = Mock()
-    final File file = new File("some-target-file")
-    final OnDemandFileAccess lock = new OnDemandFileAccess(file, "some-lock", manager)
+
+    @Rule TemporaryFolder dir = new TemporaryFolder()
+    OnDemandFileAccess lock
+    File file
+
+    def setup() {
+        file = dir.file("some-target-file")
+        lock = new OnDemandFileAccess(file, "some-lock", manager)
+    }
 
     def "acquires shared lock to read file"() {
         def action = {} as Factory
 
         when:
-        lock.readFromFile(action)
+        lock.readFile(action)
 
         then:
+        !file.exists()
         1 * manager.lock(file, LockMode.Shared, "some-lock") >> targetLock
-        1 * targetLock.readFromFile(action)
+        1 * targetLock.readFile(action)
+        1 * targetLock.close()
+        0 * targetLock._
+    }
+
+    def "acquires exclusive lock to update file"() {
+        def action = {} as Runnable
+
+        when:
+        lock.updateFile(action)
+
+        then:
+        !file.exists()
+        1 * manager.lock(file, LockMode.Exclusive, "some-lock") >> targetLock
+        1 * targetLock.updateFile(action)
         1 * targetLock.close()
         0 * targetLock._
     }
 
-    def "acquires exclusive lock to write to file"() {
+    def "acquires exclusive lock to write file"() {
         def action = {} as Runnable
 
         when:
-        lock.writeToFile(action)
+        lock.writeFile(action)
 
         then:
+        !file.exists()
         1 * manager.lock(file, LockMode.Exclusive, "some-lock") >> targetLock
-        1 * targetLock.writeToFile(action)
+        1 * targetLock.writeFile(action)
         1 * targetLock.close()
         0 * targetLock._
     }
+
+    def "can read from file"() {
+        given:
+        def access = access(file)
+        access.writeFile({})
+
+        expect:
+        access.readFile { assert !file.exists(); true }
+
+        when:
+        access.updateFile { file << "aaa" }
+
+        then:
+        access.readFile { file.text } == "aaa"
+    }
+
+    def "can write and update"() {
+        given:
+        def access = access(file)
+
+        when:
+        access.writeFile { file << "1" }
+        access.updateFile { file << "2" }
+
+        then:
+        access.readFile { file.text } == "12"
+    }
+
+    FileLockManager createManager() {
+        new DefaultFileLockManager(new DefaultProcessMetaDataProvider(new NativeServices().get(ProcessEnvironment)))
+    }
+
+    FileAccess access(File file, FileLockManager manager = createManager()) {
+        new OnDemandFileAccess(file, "some-lock", manager)
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy b/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy
index 8fb2c49..0fc5896 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/SimpleStateCacheTest.groovy
@@ -17,16 +17,22 @@ package org.gradle.cache.internal
 
 import org.gradle.cache.DefaultSerializer
 import org.gradle.cache.PersistentStateCache
-import org.gradle.cache.Serializer
+import org.gradle.messaging.serialize.Serializer
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
+import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.isIntegrityViolated
+import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.*
 
 class SimpleStateCacheTest extends Specification {
     @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
     final FileAccess fileAccess = Mock()
-    final Serializer<String> serializer = new DefaultSerializer<String>()
-    final SimpleStateCache<String> cache = new SimpleStateCache<String>(tmpDir.file("state.bin"), fileAccess, serializer)
+    final File file = tmpDir.file("state.bin")
+    final SimpleStateCache<String> cache = createStateCache(fileAccess)
+
+    private SimpleStateCache createStateCache(FileAccess fileAccess, File file = file, Serializer serializer = new DefaultSerializer()) {
+        return new SimpleStateCache(file, fileAccess, serializer)
+    }
 
     def "returns null when file does not exist"() {
         when:
@@ -34,7 +40,7 @@ class SimpleStateCacheTest extends Specification {
 
         then:
         result == null
-        1 * fileAccess.readFromFile(!null) >> { it[0].create() }
+        1 * fileAccess.readFile(!null) >> { it[0].create() }
     }
     
     def "get returns last value written to file"() {
@@ -42,7 +48,7 @@ class SimpleStateCacheTest extends Specification {
         cache.set('some value')
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { it[0].run() }
+        1 * fileAccess.writeFile(!null) >> { it[0].run() }
         tmpDir.file('state.bin').assertIsFile()
 
         when:
@@ -50,7 +56,7 @@ class SimpleStateCacheTest extends Specification {
 
         then:
         result == 'some value'
-        1 * fileAccess.readFromFile(!null) >> { it[0].create() }
+        1 * fileAccess.readFile(!null) >> { it[0].create() }
     }
 
     def "update provides access to cached value"() {
@@ -58,7 +64,7 @@ class SimpleStateCacheTest extends Specification {
         cache.set("foo")
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { it[0].run() }
+        1 * fileAccess.writeFile(!null) >> { it[0].run() }
 
         when:
         cache.update({ value ->
@@ -67,14 +73,14 @@ class SimpleStateCacheTest extends Specification {
         } as PersistentStateCache.UpdateAction)
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { it[0].run() }
+        1 * fileAccess.updateFile(!null) >> { it[0].run() }
 
         when:
         def result = cache.get()
 
         then:
         result == "foo bar"
-        1 * fileAccess.readFromFile(!null) >> { it[0].create() }
+        1 * fileAccess.readFile(!null) >> { it[0].create() }
     }
 
     def "update does not explode when no existing value"() {
@@ -85,13 +91,36 @@ class SimpleStateCacheTest extends Specification {
         } as PersistentStateCache.UpdateAction)
 
         then:
-        1 * fileAccess.writeToFile(!null) >> { it[0].run() }
+        1 * fileAccess.updateFile(!null) >> { it[0].run() }
 
         when:
         def result = cache.get()
 
         then:
         result == "bar"
-        1 * fileAccess.readFromFile(!null) >> { it[0].create() }
+        1 * fileAccess.readFile(!null) >> { it[0].create() }
+    }
+    
+    def "can set value when file integrity is violated"() {
+        given:
+        def cache = createStateCache(createOnDemandFileLock(file))
+        
+        and:
+        cache.set "a"
+        
+        expect:
+        cache.get() == "a"
+        
+        when:
+        unlockUncleanly(file)
+
+        then:
+        isIntegrityViolated(file)
+
+        when:
+        cache.set "b"
+
+        then:
+        cache.get() == "b"
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java b/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
index efcf254..ec4f539 100644
--- a/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/cache/internal/btree/BTreePersistentIndexedCacheTest.java
@@ -16,7 +16,7 @@
 package org.gradle.cache.internal.btree;
 
 import org.gradle.cache.DefaultSerializer;
-import org.gradle.cache.Serializer;
+import org.gradle.messaging.serialize.Serializer;
 import org.gradle.util.TemporaryFolder;
 import org.gradle.util.TestFile;
 import org.junit.Before;
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
index d32ee50..556878e 100755
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
@@ -17,7 +17,7 @@ package org.gradle.configuration;
 
 import org.gradle.internal.Factory;
 import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.api.internal.artifacts.dsl.ClasspathScriptTransformer;
+import org.gradle.groovy.scripts.internal.ClasspathScriptTransformer;
 import org.gradle.api.internal.initialization.ScriptHandlerFactory;
 import org.gradle.api.internal.initialization.ScriptHandlerInternal;
 import org.gradle.groovy.scripts.*;
diff --git a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
index cb60f9f..d6190be 100644
--- a/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/groovy/scripts/internal/DefaultScriptCompilationHandlerTest.java
@@ -27,7 +27,6 @@ import org.codehaus.groovy.control.CompilationFailedException;
 import org.codehaus.groovy.control.Phases;
 import org.codehaus.groovy.control.SourceUnit;
 import org.gradle.api.GradleException;
-import org.gradle.api.internal.artifacts.dsl.AbstractScriptTransformer;
 import org.gradle.api.internal.resource.Resource;
 import org.gradle.groovy.scripts.ScriptCompilationException;
 import org.gradle.groovy.scripts.ScriptSource;
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
index 68e5df3..422effd 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
@@ -25,16 +25,19 @@ import org.gradle.api.invocation.Gradle
 import org.gradle.api.plugins.Convention
 import org.gradle.cache.CacheBuilder
 import org.gradle.cache.CacheRepository
-import org.gradle.cache.ObjectCacheBuilder
-import org.gradle.cache.PersistentStateCache
+import org.gradle.cache.DirectoryCacheBuilder
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.internal.FileLockManager
 import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
 import org.jmock.api.Action
+import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertEquals
 
@@ -51,11 +54,12 @@ class BuildSourceBuilderTest {
     Project rootProjectMock = context.mock(Project.class)
     FileCollection configurationMock = context.mock(FileCollection.class)
     TestFile rootDir = tmpDir.createDir('root')
-    File testBuildSrcDir = rootDir.file('buildSrc').createDir()
-    Set testDependencies
+    TestFile testBuildSrcDir = rootDir.file('buildSrc').createDir()
+    TestFile buildSrcCache = testBuildSrcDir.createDir(".gradle/noVersion/buildSrc");
+    List testDependencies
     StartParameter expectedStartParameter
     CacheRepository cacheRepository = context.mock(CacheRepository.class)
-    PersistentStateCache stateCache = context.mock(PersistentStateCache.class)
+    PersistentCache persistentCache = context.mock(PersistentCache.class)
     BuildResult expectedBuildResult
     Gradle build = context.mock(Gradle.class)
     EmbeddableJavaProject projectMetaInfo = context.mock(EmbeddableJavaProject.class)
@@ -64,6 +68,7 @@ class BuildSourceBuilderTest {
         buildSourceBuilder = new BuildSourceBuilder(gradleFactoryMock, context.mock(ClassLoaderRegistry.class), cacheRepository)
         expectedStartParameter = new StartParameter(currentDir: testBuildSrcDir)
         testDependencies = ['dep1' as File, 'dep2' as File]
+
         Convention convention = context.mock(Convention)
         context.checking {
             allowing(build).getRootProject(); will(returnValue(rootProjectMock))
@@ -72,13 +77,13 @@ class BuildSourceBuilderTest {
             allowing(convention).getPlugin(EmbeddableJavaProject);
             will(returnValue(projectMetaInfo))
             allowing(projectMetaInfo).getRuntimeClasspath(); will(returnValue(configurationMock))
-            allowing(configurationMock).getFiles(); will(returnValue(testDependencies))
+            allowing(configurationMock).getFiles(); will(returnValue(testDependencies as Set))
         }
         expectedBuildResult = new BuildResult(build, null)
     }
 
     @Test public void testCreateClasspathWhenBuildSrcDirExistsAndHasNotBeenBuiltBefore() {
-        expectValueFetchedFromCache(null)
+        expectMarkerFileFetchedFromCache(false)
         context.checking {
             one(projectMetaInfo).getRebuildTasks(); will(returnValue(['clean', 'build']))
             one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()));
@@ -86,15 +91,15 @@ class BuildSourceBuilderTest {
             one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
             one(gradleMock).run(); will(returnValue(expectedBuildResult))
         }
-        expectValueWrittenToCache()
-        
+        expectMarkerFileInCache()
+
         createBuildFile()
-        Set<File> actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter)
+        def actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter).asFiles
         assertEquals(testDependencies, actualClasspath)
     }
 
     @Test public void testCreateClasspathWhenBuildSrcDirExistsAndHasBeenBuiltBefore() {
-        expectValueFetchedFromCache(true)
+        expectMarkerFileFetchedFromCache(true)
         context.checking {
             one(projectMetaInfo).getBuildTasks(); will(returnValue(['build']))
             one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()))
@@ -102,50 +107,67 @@ class BuildSourceBuilderTest {
             one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
             one(gradleMock).run(); will(returnValue(expectedBuildResult))
         }
-        expectValueWrittenToCache()
+        expectMarkerFileInCache()
 
-        Set actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter)
+        def actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter).asFiles
         assertEquals(testDependencies, actualClasspath)
     }
 
     @Test public void testCreateClasspathWhenBuildSrcDirDoesNotExist() {
         expectedStartParameter = expectedStartParameter.newInstance()
         expectedStartParameter.setCurrentDir(new File('nonexisting'));
-        assertEquals([] as Set, buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter))
+        assertEquals([], buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter).asFiles)
     }
 
-    private expectValueFetchedFromCache(def value) {
+    private expectMarkerFileFetchedFromCache(boolean markerFileExists) {
+        if (markerFileExists) {
+            buildSrcCache.createFile("built.bin");
+        } else {
+            new File(buildSrcCache, "built.bin").delete()
+        }
         context.checking {
-            ObjectCacheBuilder<Boolean, PersistentStateCache<Boolean>> builder = context.mock(ObjectCacheBuilder.class)
-            one(cacheRepository).stateCache(Boolean.class, 'buildSrc')
+            DirectoryCacheBuilder builder = context.mock(DirectoryCacheBuilder.class)
+            one(cacheRepository).cache('buildSrc')
             will(returnValue(builder))
 
             one(builder).forObject(testBuildSrcDir)
             will(returnValue(builder))
 
+            one(builder).withLockMode(FileLockManager.LockMode.None)
+            will(returnValue(builder))
+
             one(builder).withVersionStrategy(CacheBuilder.VersionStrategy.SharedCacheInvalidateOnVersionChange)
             will(returnValue(builder))
 
             one(builder).open()
-            will(returnValue(stateCache))
+            will(returnValue(persistentCache))
 
-            one(stateCache).get()
-            will(returnValue(value))
+            one(persistentCache).getBaseDir()
+            will(returnValue(buildSrcCache))
+            one(persistentCache).useCache(withParam(equalTo("rebuild buildSrc")), (org.gradle.internal.Factory) withParam(any(org.gradle.internal.Factory.class)))
+            will(executeBuildSrcBuild())
         }
     }
 
-    private expectValueWrittenToCache() {
-        context.checking {
-            one(stateCache).set(true)
-        }
+    private expectMarkerFileInCache() {
+        buildSrcCache.file("buildSrc.lock").exists()
     }
 
     private createBuildFile() {
         new File(testBuildSrcDir, Project.DEFAULT_BUILD_FILE).createNewFile()
     }
 
+    private Action executeBuildSrcBuild() {
+        return [invoke: {invocation -> invocation.getParameter(1).create()},
+                describeTo: {description -> }] as Action
+    }
+
     private Action notifyProjectsEvaluated() {
         return [invoke: {invocation -> invocation.getParameter(0).projectsEvaluated(build)},
                 describeTo: {description -> }] as Action
     }
+
+    @After public void cleanup() {
+        buildSrcCache.file("buildSrc.lock").delete()
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/listener/AsyncListenerBroadcastTest.groovy b/subprojects/core/src/test/groovy/org/gradle/listener/AsyncListenerBroadcastTest.groovy
deleted file mode 100755
index aa0f659..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/listener/AsyncListenerBroadcastTest.groovy
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.listener
-
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.Sequence
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-
- at RunWith(JMock.class)
-public class AsyncListenerBroadcastTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final TestListener listener1 = context.mock(TestListener.class, "listener1")
-    private final TestListener listener2 = context.mock(TestListener.class, "listener2")
-    private final AsyncListenerBroadcast broadcast = new AsyncListenerBroadcast<TestListener>(TestListener.class, executor)
-
-    @Test
-    public void deliversEventsToListenerInOrderEventsGenerated() {
-        broadcast.add(listener1)
-
-        context.checking {
-            Sequence sequence = context.sequence("seq")
-            20.times {
-                one(listener1).event("$it")
-                inSequence(sequence)
-            }
-        }
-
-        20.times {
-            broadcast.source.event("$it")
-        }
-
-        broadcast.stop()
-    }
-
-    @Test
-    public void deliversEventsToListenersInOrderListenersAdded() {
-        broadcast.add(listener1)
-        broadcast.add(listener2)
-
-        context.checking {
-            Sequence sequence = context.sequence("seq")
-            20.times {
-                one(listener1).event("$it")
-                inSequence(sequence)
-                one(listener2).event("$it")
-                inSequence(sequence)
-            }
-        }
-
-        20.times {
-            broadcast.source.event("$it")
-        }
-
-        broadcast.stop()
-    }
-
-    @Test
-    public void blockingListenerDoesNotBlockEventGeneration() {
-        broadcast.add(listener1)
-
-        context.checking {
-            one(listener1).event("1")
-            will {
-                syncAt(1)
-                syncAt(2)
-            }
-            one(listener1).event("2")
-            one(listener1).event("3")
-        }
-        
-        run {
-            broadcast.source.event("1")
-            syncAt(1)
-            broadcast.source.event("2")
-            broadcast.source.event("3")
-            syncAt(2)
-        }
-
-        broadcast.stop()
-    }
-
-    @Test
-    public void stopBlocksUntilAllEventsDelivered() {
-        broadcast.add(listener1)
-
-        context.checking {
-            one(listener1).event("1")
-            will {
-                syncAt(1)
-                syncAt(2)
-            }
-        }
-
-        broadcast.source.event("1")
-
-        run {
-            syncAt(1)
-            expectBlocksUntil(2) {
-                broadcast.stop()
-            }
-        }
-    }
-}
-
-public interface TestListener {
-    void event(String param)
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/ConsoleRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/ConsoleRendererTest.groovy
new file mode 100644
index 0000000..2f18c39
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/ConsoleRendererTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012 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 org.gradle.logging
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+import spock.lang.Specification
+
+class ConsoleRendererTest extends Specification {
+    ConsoleRenderer renderer = new ConsoleRenderer()
+
+    @Requires(TestPrecondition.NOT_WINDOWS)
+    def "produces triple-slash file URLs"() {
+        expect:
+        renderer.asClickableFileUrl(new File("/foo/bar/baz")) == "file:///foo/bar/baz"
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "produces triple-slash file URLs on Windows"() {
+        expect:
+        renderer.asClickableFileUrl(new File("C:\\foo\\bar\\baz")) == "file:///C:/foo/bar/baz"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractStyledTextOutputTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractStyledTextOutputTest.groovy
index db1736d..5914f55 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractStyledTextOutputTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/AbstractStyledTextOutputTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.logging.internal
 
 import org.gradle.logging.StyledTextOutput.Style
 import org.gradle.internal.SystemProperties
+import org.gradle.logging.TestStyledTextOutput
 
 class AbstractStyledTextOutputTest extends OutputSpecification {
     private final TestStyledTextOutput output = new TestStyledTextOutput()
@@ -174,60 +175,3 @@ class AbstractStyledTextOutputTest extends OutputSpecification {
     }
 }
 
-class TestStyledTextOutput extends AbstractStyledTextOutput {
-    StringBuilder result = new StringBuilder()
-
-    @Override
-    String toString() {
-        result.toString()
-    }
-
-    def TestStyledTextOutput ignoreStyle() {
-        return new TestStyledTextOutput() {
-            @Override protected void doStyleChange(Style style) {
-            }
-        }
-    }
-
-    def String getRawValue() {
-        return result.toString()
-    }
-
-    /**
-     * Returns the normalised value of this text output. Normalises:
-     * - style changes to {style} where _style_ is the lowercase name of the style.
-     * - line endings to \n
-     * - stack traces to {stacktrace}\n
-     */
-    def String getValue() {
-        StringBuilder normalised = new StringBuilder()
-
-        String eol = SystemProperties.lineSeparator
-        boolean inStackTrace = false
-        new StringTokenizer(result.toString().replaceAll(eol, '\n'), '\n', true).each { String line ->
-            if (line == '\n') {
-                if (!inStackTrace) {
-                    normalised.append('\n')
-                }
-            } else if (line.matches(/\s+at .+\(.+\)/)) {
-                if (!inStackTrace) {
-                    normalised.append('{stacktrace}\n')
-                }
-                inStackTrace = true
-            } else {
-                inStackTrace = false
-                normalised.append(line)
-            }
-        }
-        return normalised.toString()
-    }
-
-    @Override protected void doStyleChange(Style style) {
-        result.append("{${style.toString().toLowerCase()}}")
-    }
-
-    @Override
-    protected void doAppend(String text) {
-        result.append(text)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/ConsoleStub.java b/subprojects/core/src/test/groovy/org/gradle/logging/internal/ConsoleStub.java
index b7083f0..5982a07 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/ConsoleStub.java
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/ConsoleStub.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.logging.internal;
 
+import org.gradle.logging.TestStyledTextOutput;
+
 class ConsoleStub implements Console {
     private final TextAreaImpl mainArea = new TextAreaImpl();
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactoryTest.groovy
index 0a63d83..f932b8b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/DefaultProgressLoggerFactoryTest.groovy
@@ -17,7 +17,7 @@ package org.gradle.logging.internal
 
 import spock.lang.Specification
 import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.util.TimeProvider
+import org.gradle.internal.TimeProvider
 
 class DefaultProgressLoggerFactoryTest extends Specification {
     private final ProgressListener progressListener = Mock()
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutputTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutputTest.groovy
index 225d71a..413503b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutputTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/LoggingBackedStyledTextOutputTest.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.logging.internal
 
-import org.gradle.util.TimeProvider
+import org.gradle.internal.TimeProvider
 import static org.gradle.logging.StyledTextOutput.Style.*
 import org.gradle.api.logging.LogLevel
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/PrintStreamLoggingSystemTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/PrintStreamLoggingSystemTest.groovy
index 6ccf40d..890a6ef 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/PrintStreamLoggingSystemTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/PrintStreamLoggingSystemTest.groovy
@@ -18,7 +18,7 @@ package org.gradle.logging.internal
 
 import org.gradle.api.logging.LogLevel
 import spock.lang.Specification
-import org.gradle.util.TimeProvider
+import org.gradle.internal.TimeProvider
 
 class PrintStreamLoggingSystemTest extends Specification {
     private final OutputStream original = new ByteArrayOutputStream()
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/StyledTextOutputBackedRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/StyledTextOutputBackedRendererTest.groovy
index 043b3a9..d4e1a7c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/StyledTextOutputBackedRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/StyledTextOutputBackedRendererTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.logging.internal
 
 import org.gradle.logging.StyledTextOutput
 import org.gradle.api.logging.LogLevel
+import org.gradle.logging.TestStyledTextOutput
 
 class StyledTextOutputBackedRendererTest extends OutputSpecification {
     def rendersOutputEvent() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/TerminalDetectorFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/TerminalDetectorFactoryTest.groovy
index 560cda4..6a0937d 100755
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/TerminalDetectorFactoryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/TerminalDetectorFactoryTest.groovy
@@ -16,7 +16,6 @@
 
 package org.gradle.logging.internal;
 
-
 import org.gradle.internal.nativeplatform.NoOpTerminalDetector
 import org.gradle.internal.nativeplatform.WindowsTerminalDetector
 import org.gradle.internal.nativeplatform.jna.JnaBootPathConfigurer
@@ -31,9 +30,8 @@ import spock.lang.Specification
 /**
  * @author: Szczepan Faber, created at: 9/12/11
  */
-public class TerminalDetectorFactoryTest extends Specification {
-    @Rule
-    TemporaryFolder temp = new TemporaryFolder()
+class TerminalDetectorFactoryTest extends Specification {
+    @Rule TemporaryFolder temp
 
     @Requires([TestPrecondition.JNA, TestPrecondition.NOT_WINDOWS])
     def "should configure JNA library"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurerTest.groovy
new file mode 100644
index 0000000..0f302b0
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/logging/internal/logback/LogbackLoggingConfigurerTest.groovy
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2012 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 org.gradle.logging.internal.logback
+
+import ch.qos.logback.classic.LoggerContext
+import ch.qos.logback.classic.Level
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logging
+import org.gradle.logging.internal.OutputEventListener
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import spock.lang.Specification
+
+class LogbackLoggingConfigurerTest extends Specification {
+    Logger logger = LoggerFactory.getLogger("cat1")
+    OutputEventListener listener = Mock()
+    LogbackLoggingConfigurer configurer = new LogbackLoggingConfigurer(listener)
+    
+    def cleanup() {
+        def context = (LoggerContext) LoggerFactory.getILoggerFactory()
+        context.reset()
+    }
+
+    def routesSlf4jLogEventsToOutputEventListener() {
+        when:
+        configurer.configure(LogLevel.INFO)
+        logger.info('message')
+
+        then:
+        1 * listener.onOutput({it.category == 'cat1' && it.message == 'message' && it.logLevel == LogLevel.INFO && it.throwable == null})
+        0 * listener._
+    }
+
+    def includesThrowableInLogEvent() {
+        def failure = new RuntimeException()
+
+        when:
+        configurer.configure(LogLevel.INFO)
+        logger.info('message', failure)
+
+        then:
+        1 * listener.onOutput({it.category == 'cat1' && it.message == 'message' && it.logLevel == LogLevel.INFO && it.throwable == failure})
+        0 * listener._
+    }
+    
+    def mapsSlf4jLogLevelsToGradleLogLevels() {
+        when:
+        configurer.configure(LogLevel.DEBUG)
+
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'debug' && it.logLevel == LogLevel.DEBUG})
+        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
+        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def formatsLogMessage() {
+        when:
+        configurer.configure(LogLevel.INFO)
+        logger.info('message {} {}', 'arg1', 'arg2')
+
+        then:
+        1 * listener.onOutput({it.message == 'message arg1 arg2'})
+        0 * listener._
+    }
+
+    def attachesATimestamp() {
+        when:
+        configurer.configure(LogLevel.INFO)
+        logger.info('message')
+
+        then:
+        1 * listener.onOutput({it.timestamp >= System.currentTimeMillis() - 300})
+        0 * listener._
+    }
+
+    def filtersLifecycleAndLowerWhenConfiguredAtQuietLevel() {
+        when:
+        configurer.configure(LogLevel.QUIET)
+
+        logger.trace('trace')
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def filtersInfoAndLowerWhenConfiguredAtLifecycleLevel() {
+        when:
+        configurer.configure(LogLevel.LIFECYCLE)
+
+        logger.trace('trace')
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def filtersDebugAndLowerWhenConfiguredAtInfoLevel() {
+        when:
+        configurer.configure(LogLevel.INFO)
+
+        logger.trace('trace')
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
+        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def filtersTraceWhenConfiguredAtDebugLevel() {
+        when:
+        configurer.configure(LogLevel.DEBUG)
+
+        logger.trace('trace')
+        logger.debug('debug')
+        logger.info('info')
+        logger.info(Logging.LIFECYCLE, 'lifecycle')
+        logger.info(Logging.QUIET, 'quiet')
+        logger.warn('warn')
+        logger.error('error')
+
+        then:
+        1 * listener.onOutput({it.message == 'debug' && it.logLevel == LogLevel.DEBUG})
+        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
+        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
+        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
+        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
+        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
+        0 * listener._
+    }
+
+    def "turns off Apache HTTP wire logging"() {
+        def wireLogger = LoggerFactory.getLogger("org.apache.http.wire")
+        configurer.configure(LogLevel.DEBUG)
+
+        when:
+        wireLogger.debug("debug message")
+
+        then:
+        0 * _
+
+        // check that changing log level doesn't reactivate wire logging
+        when:
+        configurer.configure(LogLevel.INFO)
+        wireLogger.info("info message")
+
+        then:
+        0 * _
+    }
+
+    def "respects per-logger log level if either logger log level or event log level is one of OFF, ERROR, DEBUG, TRACE"() {
+        configurer.configure(LogLevel.INFO)
+        logger.level = Level.DEBUG
+
+        when:
+        logger.debug("message")
+
+        then:
+        1 * listener._
+
+        when:
+        logger.level = Level.ERROR
+        logger.warn("message")
+
+        then:
+        0 * listener._
+    }
+
+    def "respects per-logger log level if both logger log level and event log level are WARN"() {
+        configurer.configure(LogLevel.ERROR)
+        logger.level = Level.WARN
+
+        when:
+        logger.warn("message")
+
+        then:
+        1 * listener._
+    }
+
+    // More a limitation than a feature, although it might not matter much for Gradle, where individual
+    // loggers usually don't have a log level set.
+    def "does not respect per-logger log level if either logger log level or event log level is one of INFO, LIFECYCLE, WARN, QUIET"() {
+        configurer.configure(LogLevel.INFO)
+        logger.level = Level.WARN
+
+        when:
+        logger.info("message")
+
+        then:
+        1 * listener._
+
+        when:
+        configurer.configure(LogLevel.WARN)
+        logger.level = Level.INFO
+        logger.info("message")
+
+        then:
+        0 * listener._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurerTest.groovy
deleted file mode 100644
index bd73d0f..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/logging/internal/slf4j/Slf4jLoggingConfigurerTest.groovy
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.logging.internal.slf4j
-
-import ch.qos.logback.classic.LoggerContext
-import org.gradle.api.logging.LogLevel
-import org.gradle.api.logging.Logging
-import org.gradle.logging.internal.OutputEventListener
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import spock.lang.Specification
-
-class Slf4jLoggingConfigurerTest extends Specification {
-    private final Logger logger = LoggerFactory.getLogger("cat1");
-    private final OutputEventListener listener = Mock()
-    private final Slf4jLoggingConfigurer configurer = new Slf4jLoggingConfigurer(listener)
-    
-    def cleanup() {
-        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
-        lc.reset();
-    }
-
-    def routesSlf4jLogEventsToOutputEventListener() {
-        when:
-        configurer.configure(LogLevel.INFO)
-        logger.info('message')
-
-        then:
-        1 * listener.onOutput({it.category == 'cat1' && it.message == 'message' && it.logLevel == LogLevel.INFO && it.throwable == null})
-        0 * listener._
-    }
-
-    def includesThrowableInLogEvent() {
-        def failure = new RuntimeException()
-
-        when:
-        configurer.configure(LogLevel.INFO)
-        logger.info('message', failure)
-
-        then:
-        1 * listener.onOutput({it.category == 'cat1' && it.message == 'message' && it.logLevel == LogLevel.INFO && it.throwable == failure})
-        0 * listener._
-    }
-    
-    def mapsSlf4jLogLevelsToGradleLogLevels() {
-        when:
-        configurer.configure(LogLevel.DEBUG)
-
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'debug' && it.logLevel == LogLevel.DEBUG})
-        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
-        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-
-    def formatsLogMessage() {
-        when:
-        configurer.configure(LogLevel.INFO)
-        logger.info('message {} {}', 'arg1', 'arg2')
-
-        then:
-        1 * listener.onOutput({it.message == 'message arg1 arg2'})
-        0 * listener._
-    }
-
-    def attachesATimestamp() {
-        when:
-        configurer.configure(LogLevel.INFO)
-        logger.info('message')
-
-        then:
-        1 * listener.onOutput({it.timestamp >= System.currentTimeMillis() - 300})
-        0 * listener._
-    }
-
-    def filtersLifecycleAndLowerWhenConfiguredAtQuietLevel() {
-        when:
-        configurer.configure(LogLevel.QUIET)
-
-        logger.trace('trace')
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-
-    def filtersInfoAndLowerWhenConfiguredAtLifecycleLevel() {
-        when:
-        configurer.configure(LogLevel.LIFECYCLE)
-
-        logger.trace('trace')
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-
-    def filtersDebugAndLowerWhenConfiguredAtInfoLevel() {
-        when:
-        configurer.configure(LogLevel.INFO)
-
-        logger.trace('trace')
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
-        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-
-    def filtersTraceWhenConfiguredAtDebugLevel() {
-        when:
-        configurer.configure(LogLevel.DEBUG)
-
-        logger.trace('trace')
-        logger.debug('debug')
-        logger.info('info')
-        logger.info(Logging.LIFECYCLE, 'lifecycle')
-        logger.info(Logging.QUIET, 'quiet')
-        logger.warn('warn')
-        logger.error('error')
-
-        then:
-        1 * listener.onOutput({it.message == 'debug' && it.logLevel == LogLevel.DEBUG})
-        1 * listener.onOutput({it.message == 'info' && it.logLevel == LogLevel.INFO})
-        1 * listener.onOutput({it.message == 'lifecycle' && it.logLevel == LogLevel.LIFECYCLE})
-        1 * listener.onOutput({it.message == 'quiet' && it.logLevel == LogLevel.QUIET})
-        1 * listener.onOutput({it.message == 'warn' && it.logLevel == LogLevel.WARN})
-        1 * listener.onOutput({it.message == 'error' && it.logLevel == LogLevel.ERROR})
-        0 * listener._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactoryTest.groovy
deleted file mode 100644
index 3ed569b..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactoryTest.groovy
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.concurrent
-
-import static org.hamcrest.Matchers.*
-
-import java.util.concurrent.ExecutorService
-import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.MultithreadedTestCase
-import org.jmock.integration.junit4.JMock
-import org.junit.After
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.junit.Assert.*
-import java.util.concurrent.TimeUnit
-
- at RunWith(JMock)
-class DefaultExecutorFactoryTest extends MultithreadedTestCase {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final DefaultExecutorFactory factory = new DefaultExecutorFactory() {
-        def ExecutorService createExecutor(String displayName) {
-            return getExecutor()
-        }
-    }
-
-    @After
-    public void tearDown() {
-        factory.stop()
-    }
-
-    @Test
-    public void stopBlocksUntilAllJobsAreComplete() {
-        Runnable runnable = context.mock(Runnable.class)
-
-        context.checking {
-            one(runnable).run()
-            will {
-                syncAt(1)
-            }
-        }
-
-        def executor = factory.create('<display-name>')
-        executor.execute(runnable)
-
-        run {
-            expectBlocksUntil(1) {
-                executor.stop()
-            }
-        }
-    }
-    
-    @Test
-    public void factoryStopBlocksUntilAllJobsAreComplete() {
-        Runnable runnable = context.mock(Runnable.class)
-
-        context.checking {
-            one(runnable).run()
-            will {
-                syncAt(1)
-            }
-        }
-
-        def executor = factory.create('<display-name>')
-        executor.execute(runnable)
-
-        run {
-            expectBlocksUntil(1) {
-                factory.stop()
-            }
-        }
-    }
-
-    @Test
-    public void stopRethrowsFirstExecutionException() {
-        Runnable runnable = context.mock(Runnable.class)
-        RuntimeException failure = new RuntimeException()
-
-        context.checking {
-            one(runnable).run()
-            will {
-                throw failure
-            }
-        }
-
-        def executor = factory.create('<display-name>')
-        executor.execute(runnable)
-
-        try {
-            executor.stop()
-            fail()
-        } catch (RuntimeException e) {
-            assertThat(e, sameInstance(failure))
-        }
-    }
-
-    @Test
-    public void stopThrowsExceptionOnTimeout() {
-        Runnable runnable = context.mock(Runnable.class)
-
-        context.checking {
-            one(runnable).run()
-            will {
-                Thread.sleep(1000)
-            }
-        }
-
-        def executor = factory.create('<display-name>')
-        executor.execute(runnable)
-
-        expectTimesOut(500, TimeUnit.MILLISECONDS) {
-            try {
-                executor.stop(500, TimeUnit.MILLISECONDS)
-                fail()
-            } catch (IllegalStateException e) {
-                assertThat(e.message, equalTo('Timeout waiting for concurrent jobs to complete.'))
-            }
-        }
-    }
-
-    @Test
-    public void cannotStopExecutorFromAnExecutorThread() {
-        Runnable runnable = context.mock(Runnable.class)
-
-        def executor = factory.create('<display-name>')
-
-        context.checking {
-            one(runnable).run()
-            will {
-                executor.stop()
-            }
-        }
-
-        executor.execute(runnable)
-
-        try {
-            executor.stop()
-            fail()
-        } catch (IllegalStateException e) {
-            assertThat(e.message, equalTo('Cannot stop this executor from an executor thread.'))
-        }
-
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java
deleted file mode 100755
index 7219366..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.dispatch;
-
-import org.junit.Test;
-
-import static org.gradle.util.Matchers.*;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-public class MethodInvocationTest {
-    @Test
-    public void equalsAndHashCode() throws Exception {
-        MethodInvocation invocation = new MethodInvocation(String.class.getMethod("length"), new Object[]{"param"});
-        MethodInvocation equalInvocation = new MethodInvocation(String.class.getMethod("length"), new Object[]{"param"});
-        MethodInvocation differentMethod = new MethodInvocation(String.class.getMethod("getBytes"), new Object[]{"param"});
-        MethodInvocation differentArgs = new MethodInvocation(String.class.getMethod("length"), new Object[]{"a", "b"});
-        assertThat(invocation, strictlyEqual(equalInvocation));
-        assertThat(invocation, not(equalTo(differentMethod)));
-        assertThat(invocation, not(equalTo(differentArgs)));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy
deleted file mode 100644
index ab01369..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.messaging.remote.internal.protocol.Request
-import org.gradle.messaging.remote.internal.protocol.ConsumerAvailable
-import org.gradle.messaging.remote.internal.protocol.ConsumerUnavailable
-import java.util.concurrent.TimeUnit
-
-class BroadcastSendProtocolTest extends Specification {
-    final ProtocolContext<Message> context = Mock()
-    final BroadcastSendProtocol protocol = new BroadcastSendProtocol()
-
-    def setup() {
-        protocol.start(context)
-    }
-
-    def "queues outgoing messages until a consumer is available"() {
-        when:
-        protocol.handleOutgoing(new Request("channel", "message1"))
-        protocol.handleOutgoing(new Request("channel", "message2"))
-
-        then:
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-
-        then:
-        1 * context.dispatchOutgoing(new Request("id", "message1"))
-        1 * context.dispatchOutgoing(new Request("id", "message2"))
-        0 * context._
-    }
-
-    def "dispatches outgoing message to each consumer"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("id1", "display", "channel"))
-        protocol.handleIncoming(new ConsumerAvailable("id2", "display", "channel"))
-
-        when:
-        protocol.handleOutgoing(new Request("channel", "message"))
-
-        then:
-        1 * context.dispatchOutgoing(new Request("id1", "message"))
-        1 * context.dispatchOutgoing(new Request("id2", "message"))
-        0 * context._
-    }
-
-    def "stops dispatching to a consumer when it becomes unavailable"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-
-        when:
-        protocol.handleIncoming(new ConsumerUnavailable("id"))
-        protocol.handleOutgoing(new Request("channel", "message"))
-
-        then:
-        0 * context._
-    }
-
-    def "stop waits until for a consumer to become available and queued messages dispatched"() {
-        given:
-        protocol.handleOutgoing(new Request("channel", "message"))
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.stopLater()
-        1 * context.callbackLater(5, TimeUnit.SECONDS, !null)
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-
-        then:
-        1 * context.dispatchOutgoing(new Request("id", "message"))
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "stop waits until timeout for a consumer to become available and queued messages dispatched"() {
-        Runnable callback
-
-        when:
-        protocol.handleOutgoing(new Request("channel", "message"))
-        protocol.stopRequested()
-
-        then:
-        1 * context.callbackLater(5, TimeUnit.SECONDS, !null) >> { callback = it[2]; return null }
-
-        when:
-        callback.run()
-
-        then:
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "stops immediately when no messages have been dispatched"() {
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "stops immediately when all messages have been dispatched"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-        protocol.handleOutgoing(new Request("channel", "message"))
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.stopped()
-        0 * context._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
deleted file mode 100755
index 8bfbb83..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal;
-
-import org.gradle.messaging.concurrent.AsyncStoppable;
-import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.dispatch.MethodInvocation;
-import org.gradle.messaging.remote.Address;
-import org.gradle.messaging.remote.Addressable;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.gradle.util.Matchers.strictlyEqual;
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
- at RunWith(JMock.class)
-public class DefaultObjectConnectionTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private DefaultObjectConnection sender;
-    private DefaultObjectConnection receiver;
-    private final Addressable messageConnection = context.mock(Addressable.class);
-    private final AsyncStoppable stopControl = context.mock(AsyncStoppable.class);
-    private final TestConnection connection = new TestConnection();
-
-    @Before
-    public void setUp() {
-        IncomingMethodInvocationHandler senderIncoming = new IncomingMethodInvocationHandler(connection.getSender());
-        IncomingMethodInvocationHandler receiverIncoming = new IncomingMethodInvocationHandler(connection.getReceiver());
-        OutgoingMethodInvocationHandler senderOutgoing = new OutgoingMethodInvocationHandler(connection.getSender());
-        OutgoingMethodInvocationHandler receiverOutgoing = new OutgoingMethodInvocationHandler(connection.getReceiver());
-        sender = new DefaultObjectConnection(messageConnection, stopControl, senderOutgoing, senderIncoming);
-        receiver = new DefaultObjectConnection(messageConnection, stopControl, receiverOutgoing, receiverIncoming);
-    }
-
-    @Test
-    public void createsProxyForOutgoingType() throws Exception {
-        // Setup
-        final TestRemote handler = context.mock(TestRemote.class);
-        receiver.addIncoming(TestRemote.class, handler);
-
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        assertThat(proxy, strictlyEqual(proxy));
-        assertThat(proxy.toString(), equalTo("TestRemote broadcast"));
-    }
-
-    @Test
-    public void deliversMethodInvocationsOnOutgoingObjectToHandlerObject() throws Exception {
-        final TestRemote handler = context.mock(TestRemote.class);
-        context.checking(new Expectations() {{
-            one(handler).doStuff("param");
-        }});
-        receiver.addIncoming(TestRemote.class, handler);
-
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        proxy.doStuff("param");
-    }
-
-    @Test
-    public void deliversMethodInvocationsOnOutgoingObjectToHandlerDispatch() throws Exception {
-        final Dispatch<MethodInvocation> handler = context.mock(Dispatch.class);
-        context.checking(new Expectations() {{
-            one(handler).dispatch(new MethodInvocation(TestRemote.class.getMethod("doStuff", String.class),
-                    new Object[]{"param"}));
-        }});
-        receiver.addIncoming(TestRemote.class, handler);
-
-        TestRemote proxy = sender.addOutgoing(TestRemote.class);
-        proxy.doStuff("param");
-    }
-
-    @Test
-    public void canHaveMultipleOutgoingTypes() throws Exception {
-        final TestRemote handler1 = context.mock(TestRemote.class);
-        final TestRemote2 handler2 = context.mock(TestRemote2.class);
-
-        context.checking(new Expectations() {{
-            one(handler1).doStuff("handler 1");
-            one(handler2).doStuff("handler 2");
-        }});
-        receiver.addIncoming(TestRemote.class, handler1);
-        receiver.addIncoming(TestRemote2.class, handler2);
-
-        TestRemote remote1 = sender.addOutgoing(TestRemote.class);
-        TestRemote2 remote2 = sender.addOutgoing(TestRemote2.class);
-
-        remote1.doStuff("handler 1");
-        remote2.doStuff("handler 2");
-    }
-
-    @Test
-    public void handlesTypesWithSuperTypes() {
-        final TestRemote3 handler = context.mock(TestRemote3.class);
-
-        context.checking(new Expectations() {{
-            one(handler).doStuff("handler 1");
-        }});
-        receiver.addIncoming(TestRemote3.class, handler);
-
-        TestRemote3 remote1 = sender.addOutgoing(TestRemote3.class);
-
-        remote1.doStuff("handler 1");
-    }
-
-    @Test
-    public void cannotRegisterMultipleHandlerObjectsWithSameType() {
-        TestRemote handler = context.mock(TestRemote.class);
-        receiver.addIncoming(TestRemote.class, handler);
-
-        try {
-            receiver.addIncoming(TestRemote.class, handler);
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
-        }
-    }
-
-    @Test
-    public void cannotRegisterMultipleHandlerObjectsWithOverlappingMethods() {
-        receiver.addIncoming(TestRemote3.class, context.mock(TestRemote3.class));
-
-        try {
-            receiver.addIncoming(TestRemote.class, context.mock(TestRemote.class));
-            fail();
-        } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
-        }
-    }
-
-    @Test
-    public void canCreateMultipleOutgoingObjectsWithSameType() {
-        sender.addOutgoing(TestRemote.class);
-        sender.addOutgoing(TestRemote.class);
-    }
-
-    @Test
-    public void stopsConnectionOnStop() {
-        context.checking(new Expectations() {{
-            one(stopControl).stop();
-        }});
-
-        receiver.stop();
-    }
-
-    private class TestConnection {
-        Map<Object, Dispatch<Object>> channels = new HashMap<Object, Dispatch<Object>>();
-
-        MultiChannelConnection<Object> getSender() {
-            return new MultiChannelConnection<Object>() {
-                public Dispatch<Object> addOutgoingChannel(String channelKey) {
-                    return channels.get(channelKey);
-                }
-
-                public void addIncomingChannel(String channelKey, Dispatch<Object> dispatch) {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void requestStop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void stop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public Address getLocalAddress() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public Address getRemoteAddress() {
-                    throw new UnsupportedOperationException();
-                }
-            };
-        }
-
-        MultiChannelConnection<Object> getReceiver() {
-            return new MultiChannelConnection<Object>() {
-                public Dispatch<Object> addOutgoingChannel(String channelKey) {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void addIncomingChannel(String channelKey, Dispatch<Object> dispatch) {
-                    channels.put(channelKey, dispatch);
-                }
-
-                public void requestStop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public void stop() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public Address getLocalAddress() {
-                    throw new UnsupportedOperationException();
-                }
-
-                public Address getRemoteAddress() {
-                    throw new UnsupportedOperationException();
-                }
-            };
-        }
-    }
-
-    public interface TestRemote {
-        void doStuff(String param);
-    }
-
-    public interface TestRemote2 {
-        void doStuff(String param);
-    }
-
-    public interface TestRemote3 extends TestRemote {
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy
deleted file mode 100644
index fe36ab4..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal
-
-import org.gradle.messaging.concurrent.DefaultExecutorFactory
-import java.util.concurrent.LinkedBlockingQueue
-import java.util.concurrent.CountDownLatch
-
-import spock.lang.*
-import spock.util.concurrent.*
-import org.spockframework.runtime.SpockTimeoutError
-
-class DisconnectAwareConnectionDecoratorTest extends Specification {
-
-    def messageQueue = new LinkedBlockingQueue()
-
-    def rawConnection = new Connection() {
-        void stop() { disconnect() }
-        void requestStop() { disconnect() }
-        def receive() { 
-            def val = messageQueue.take().first()
-            if (val == null) {
-                messageQueue.put([null])
-            }
-            val
-        }
-        void dispatch(message) {}
-    }
-    def connection
-    
-    def connection() {
-        new DisconnectAwareConnectionDecorator(rawConnection, new DefaultExecutorFactory().create("test"))
-    }
-
-    void sendMessage(message = 1) {
-        messageQueue.put([message])
-    }
-
-    void disconnect() {
-        messageQueue.put([null])
-    }
-
-    def receive() {
-        connection.receive()
-    }
-
-    def disconnectedHolder = new BlockingVariable(3) // allow 3 seconds for the disconnect handler to fire
-
-    def onDisconnect(Closure action = { disconnectedHolder.set(true) }) {
-        connection.onDisconnect(action)
-    }
-
-    boolean isDisconnectHandlerDidFire() {
-        try {
-            disconnectedHolder.get()
-        } catch (SpockTimeoutError e) {
-            false
-        }
-    }
-
-    def setup() {
-        connection = connection()
-        onDisconnect() // install the default handler
-    }
-
-    def "normal send and receive"() {
-        when:
-        sendMessage(1)
-
-        then:
-        receive() == 1
-    }
-
-    def "disconnect after send and before message"() {
-        when:
-        sendMessage(1)
-
-        and:
-        disconnect()
-
-        then:
-        disconnectHandlerDidFire
-
-        and:
-        receive() == 1
-    }
-
-    def "disconnect before sending any messages"() {
-        when:
-        disconnect()
-
-        then:
-        disconnectHandlerDidFire
-
-        and:
-        receive() == null
-    }
-
-    def "stopping connection does not fire handler"() {
-        given:
-        sendMessage(1)
-        sendMessage(2)
-
-        when:
-        sleep 1000 // wait for the messages to be consumed by the buffer
-        connection.stop()
-
-        then:
-        receive() == 1
-        receive() == 2
-        receive() == null
-
-        and:
-        !disconnectHandlerDidFire
-    }
-
-
-    @Timeout(10)
-    def "receive does not return null until disconnect handler set and complete"() {
-        given:
-        connection = connection() // default connection has the default disconnect handler, create a new one with no handler
-        disconnect()
-        
-        def disconnectLatch = new CountDownLatch(1)
-        def receiveLatch = new CountDownLatch(1)
-        
-        and:
-        def received = []
-        Thread.start { received << receive(); receiveLatch.countDown() }
-
-        when:
-        sleep 1000
-
-        then:
-        received.empty // receive() should be blocked, waiting for the disconnect handler
-
-        when:
-        onDisconnect { disconnectLatch.await(); }
-
-        then:
-        received.empty // receive() should still be blocked because the disconnect handler hasn't completed
-
-        when:
-        disconnectLatch.countDown()
-        receiveLatch.await()
-
-        then:
-        received.size() == 1
-        received[0] == null
-    }
-
-    def cleanup() {
-        connection.stop()
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy
deleted file mode 100644
index 99612d9..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal
-
-import org.gradle.messaging.dispatch.Receive
-import org.gradle.messaging.concurrent.DefaultExecutorFactory
-
-import spock.lang.*
-
-class EagerReceiveBufferTest extends Specification {
-
-    def bufferSize = null
-    def receivers = []
-    def buffer
-
-    def receiver(Object... messages) {
-        def list = messages as LinkedList
-        receiver { list.poll() }
-    }
-
-    def receiver(Closure receiveImpl) {
-        receivers << (receiveImpl as Receive)
-    }
-
-    def executor() {
-        new DefaultExecutorFactory().create("test")
-    }
-
-    void bufferSize(int bufferSize) {
-        this.bufferSize = bufferSize
-    }
-
-    def buffer(Receive... receivers) {
-        if (bufferSize == null) {
-            new EagerReceiveBuffer(executor(), receivers as List)
-        } else {
-            new EagerReceiveBuffer(executor(), bufferSize, receivers as List)
-        }
-    }
-
-    def receive() {
-        if (buffer == null) {
-            buffer = buffer(*receivers)
-            buffer.start()
-        }
-
-        buffer.receive()
-    }
-
-    def "messages are consumed in order"() {
-        when:
-        receiver 1, 2, 3
-
-        then:
-        receive() == 1
-        receive() == 2
-        receive() == 3
-        receive() == null
-        receive() == null
-    }
-
-    def "messages are consumed from all receivers"() {
-        when:
-        receiver 1,2,3
-        receiver 4,5,6
-
-        then:
-        def messages = (1..6).collect { receive() }
-        def grouped = messages.groupBy { it < 4 ? "first" : "second" }
-        grouped.first == [1,2,3]
-        grouped.second == [4,5,6]
-    }
-
-    def "consumption blocks while the buffer is full"() {
-        given:
-        bufferSize 1
-
-        when:
-        def messages = new LinkedList([1,2,3,4])
-        receiver { messages.poll() }
-
-        then:
-        receive() == 1 // triggers consumption
-        sleep 1000 // enough time for the consumer thread to receive from our receiver, and block waiting for free buffer space
-        messages == [4] // 2 is on the buffer, 3 is being held waiting for space, 4 hasn't been received yet
-        receive() == 2
-        sleep 1000 // enough time for 3 to be put on the queue, and 4 to be received and held waiting for space
-        messages.empty
-        receive() == 3
-        receive() == 4
-    }
-
-    def "filling the buffer doesn't cause problems"() {
-        given:
-        bufferSize 1
-
-        when:
-        receiver 1,2,3
-        receiver 4,5,6
-        receiver 7,8,9
-
-        then:
-        9.times { assert receive() in 1..9; sleep 100 }
-    }
-
-    def "messages held while waiting for buffer space are discarded when stopped"() {
-        given:
-        bufferSize 1
-
-        when:
-        receiver 1,2,3,4
-
-        then:
-        receive() == 1
-        sleep 1000
-        buffer.stop()
-        receive() == 2
-        receive() == 3
-        receive() == null
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/InputForwarderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/InputForwarderTest.groovy
deleted file mode 100644
index a63d2e4..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/InputForwarderTest.groovy
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal
-
-import org.gradle.api.Action
-import org.gradle.messaging.concurrent.DefaultExecutorFactory
-import java.util.concurrent.LinkedBlockingQueue
-import java.util.concurrent.TimeUnit
-import static org.gradle.util.TextUtil.*
-
-import spock.lang.*
-import spock.util.concurrent.BlockingVariable
-
-class InputForwarderTest extends Specification {
-
-    def bufferSize = 1024
-    def executerFactory = new DefaultExecutorFactory()
-
-    def source = new PipedOutputStream()
-    def inputStream = new PipedInputStream(source)
-
-    def received = new LinkedBlockingQueue()
-    def finishedHolder = new BlockingVariable(2)
-
-    def action = action { received << it }
-    def onFinish = finished { finishedHolder.set(true) }
-
-    def action(Closure action) {
-        this.action = action as Action
-    }
-
-    def finished(Closure runnable) {
-        this.onFinish = runnable
-    }
-
-    def forwarder
-
-    def createForwarder() {
-        forwarder = new InputForwarder(inputStream, action, onFinish, executerFactory, bufferSize)
-        forwarder.start()
-    }
-
-    def receive(receive) {
-        assert received.poll(5, TimeUnit.SECONDS) == toPlatformLineSeparators(receive)
-        true
-    }
-
-    void input(str) {
-        source << toPlatformLineSeparators(str)
-    }
-    
-    boolean isFinished() {
-        finishedHolder.get() == true
-    }
-
-    boolean isNoMoreInput() {
-        receive null
-    }
-
-    def setup() {
-        createForwarder()
-    }
-
-    def closeInput() {
-        inputStream.close()
-        source.close()
-    }
-
-    def waitForForwarderToCollect() {
-        sleep 1000
-    }
-
-    def "input from source is forwarded until forwarder is stopped"() {
-        when:
-        input "abc\ndef\njkl"
-        waitForForwarderToCollect()
-        forwarder.stop()
-
-        then:
-        receive "abc\n"
-        receive "def\n"
-        receive "jkl"
-        noMoreInput
-
-        and:
-        finished
-    }
-
-    def "input from source is forwarded until source input stream is closed"() {
-        when:
-        input "abc\ndef\njkl"
-        waitForForwarderToCollect()
-        closeInput()
-
-        then:
-        receive "abc\n"
-        receive "def\n"
-        receive "jkl"
-        noMoreInput
-
-        and:
-        finished
-    }
-
-    def "output is buffered by line"() {
-        when:
-        input "a"
-
-        then:
-        noMoreInput
-
-        when:
-        input "b"
-
-        then:
-        noMoreInput
-
-        when:
-        input "\n"
-
-        then:
-        receive "ab\n"
-    }
-
-    def "one partial line when input stream closed gets forwarded"() {
-        when:
-        input "abc"
-        waitForForwarderToCollect()
-
-        and:
-        closeInput()
-
-        then:
-        receive "abc"
-
-        and:
-        noMoreInput
-    }
-
-    def "one partial line when forwarder stopped gets forwarded"() {
-        when:
-        input "abc"
-        waitForForwarderToCollect()
-
-        and:
-        forwarder.stop()
-
-        then:
-        receive "abc"
-
-        and:
-        noMoreInput
-    }
-
-    def "forwarder can be closed before receiving any output"() {
-        when:
-        forwarder.stop()
-
-        then:
-        noMoreInput
-    }
-
-    def "can handle lines larger than the buffer size"() {
-        given:
-        def longLine = "a" * (bufferSize * 10) + "\n"
-
-        when:
-        input longLine
-        input longLine
-
-        then:
-        receive longLine
-        receive longLine
-        noMoreInput
-    }
-
-    def cleanup() {
-        closeInput()
-        forwarder.stop()
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy
deleted file mode 100644
index a379613..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.messaging.remote.internal.protocol.*
-
-class ReceiveProtocolTest extends Specification {
-    final ProtocolContext<Message> context = Mock()
-    final ReceiveProtocol protocol = new ReceiveProtocol("id", "display", "channel")
-
-    def setup() {
-        protocol.start(context)
-    }
-
-    def "dispatches incoming consumer available message on start"() {
-        when:
-        protocol.start(context)
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerAvailable("id", "display", "channel"))
-        0 * context._
-    }
-
-    def "acknowledges outgoing producer ready message"() {
-        when:
-        protocol.handleIncoming(new ProducerReady("producer", "id"))
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerReady("id", "producer"))
-        0 * context._
-    }
-
-    def "forwards outgoing request to consumer"() {
-        Message message = Mock()
-        def request = new Request("id", message)
-
-        when:
-        protocol.handleIncoming(request)
-
-        then:
-        1 * context.dispatchIncoming(request)
-        0 * context._
-    }
-
-    def "dispatches incoming consumer stopping to all producers on worker stop and waits for acknowledgements"() {
-        given:
-        protocol.handleIncoming(new ProducerReady("producer1", "id"))
-        protocol.handleIncoming(new ProducerReady("producer2", "id"))
-
-        when:
-        protocol.handleOutgoing(new WorkerStopping())
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerStopping("id", "producer1"))
-        1 * context.dispatchOutgoing(new ConsumerStopping("id", "producer2"))
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ProducerStopped("producer1", "id"))
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerStopped("id", "producer1"))
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ProducerStopped("producer2", "id"))
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerStopped("id", "producer2"))
-        1 * context.dispatchOutgoing(new ConsumerUnavailable("id"))
-        1 * context.dispatchIncoming(new EndOfStreamEvent())
-        0 * context._
-    }
-
-    def "acknowledges outgoing producer stopped message"() {
-        given:
-        protocol.handleIncoming(new ProducerReady("producer", "id"))
-
-        when:
-        protocol.handleIncoming(new ProducerStopped("producer", "id"))
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerStopped("id", "producer"))
-        0 * context._
-    }
-
-    def "worker stop does not dispatch consumer stopping to producer which has stopped"() {
-        given:
-        protocol.handleIncoming(new ProducerReady("producer", "id"))
-        protocol.handleIncoming(new ProducerStopped("producer", "id"))
-
-        when:
-        protocol.handleOutgoing(new WorkerStopping())
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerUnavailable("id"))
-        1 * context.dispatchIncoming(new EndOfStreamEvent())
-        0 * context._
-    }
-
-    def "worker stop does not dispatch consumer stopping to producer which becomes unavailable"() {
-        given:
-        protocol.handleIncoming(new ProducerReady("producer", "id"))
-        protocol.handleIncoming(new ProducerUnavailable("producer"))
-
-        when:
-        protocol.handleOutgoing(new WorkerStopping())
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerUnavailable("id"))
-        1 * context.dispatchIncoming(new EndOfStreamEvent())
-        0 * context._
-    }
-
-    def "worker stop does not wait for producer which becomes unavailable during stop"() {
-        given:
-        protocol.handleIncoming(new ProducerReady("producer", "id"))
-        protocol.handleOutgoing(new WorkerStopping())
-
-        when:
-        protocol.handleIncoming(new ProducerUnavailable("producer"))
-
-        then:
-        1 * context.dispatchOutgoing(new ConsumerUnavailable("id"))
-        1 * context.dispatchIncoming(new EndOfStreamEvent())
-        0 * context._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy
deleted file mode 100644
index f19abef..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.messaging.remote.internal.protocol.*
-
-class SendProtocolTest extends Specification {
-    final ProtocolContext<Object> context = Mock()
-    final SendProtocol protocol = new SendProtocol("id", "display", "channel")
-
-    def setup() {
-        protocol.start(context)
-    }
-
-    def "dispatches outgoing producer available message on start"() {
-        when:
-        protocol.start(context)
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerAvailable("id", "display", "channel"))
-        0 * context._
-    }
-
-    def "dispatches outgoing producer ready when incoming consumer available received"() {
-        when:
-        protocol.handleIncoming(new ConsumerAvailable("consumer", "consumer-display", "channel"))
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerReady("id", "consumer"))
-        0 * context._
-    }
-
-    def "dispatches incoming consumer available when consumer ready received"() {
-        def available = new ConsumerAvailable("consumer", "display", "channel")
-
-        given:
-        protocol.handleIncoming(available)
-
-        when:
-        protocol.handleIncoming(new ConsumerReady("consumer", "id"))
-
-        then:
-        1 * context.dispatchIncoming(available)
-        0 * context._
-    }
-
-    def "dispatches incoming consumer unavailable and outgoing producer stopped when consumer stopping received"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
-
-        when:
-        protocol.handleIncoming(new ConsumerStopping("consumer", "id"))
-
-        then:
-        1 * context.dispatchIncoming(new ConsumerUnavailable("consumer"))
-        1 * context.dispatchOutgoing(new ProducerStopped("id", "consumer"))
-        0 * context._
-    }
-
-    def "stop dispatches outgoing producer stopped to all consumers and waits for acknowledgement"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("consumer1", "display", "channel"))
-        protocol.handleIncoming(new ConsumerReady("consumer1", "id"))
-        protocol.handleIncoming(new ConsumerAvailable("consumer2", "display", "channel"))
-        protocol.handleIncoming(new ConsumerReady("consumer2", "id"))
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerStopped("id", "consumer1"))
-        1 * context.dispatchOutgoing(new ProducerStopped("id", "consumer2"))
-        1 * context.stopLater()
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ConsumerStopped("consumer1", "id"))
-
-        then:
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ConsumerStopped("consumer2", "id"))
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "stops when no consumers"() {
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "does not dispatch stopped message to consumer which has stopped"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
-        protocol.handleIncoming(new ConsumerStopping("consumer", "id"))
-        protocol.handleIncoming(new ConsumerStopped("consumer", "id"))
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "handles consumer which becomes unavailable while waiting for consumer ready"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
-
-        when:
-        protocol.handleIncoming(new ConsumerUnavailable("consumer"))
-
-        then:
-        0 * context._
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "handles consumer which becomes unavailable while waiting for consumer stopped"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
-        protocol.handleIncoming(new ConsumerReady("consumer", "id"))
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerStopped("id", "consumer"))
-        1 * context.stopLater()
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ConsumerUnavailable("consumer"))
-
-        then:
-        1 * context.dispatchIncoming(new ConsumerUnavailable("consumer"))
-        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "handles consumer which becomes unavailable without consumer stopping message received"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("consumer", "display", "channel"))
-        protocol.handleIncoming(new ConsumerReady("consumer", "id"))
-
-        when:
-        protocol.handleIncoming(new ConsumerUnavailable("consumer"))
-
-        then:
-        1 * context.dispatchIncoming(new ConsumerUnavailable("consumer"))
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.dispatchOutgoing(new ProducerUnavailable("id"))
-        1 * context.stopped()
-        0 * context._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy
deleted file mode 100644
index 0bb563d..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal
-
-import spock.lang.Specification
-import org.gradle.messaging.remote.internal.protocol.ConsumerAvailable
-import org.gradle.messaging.remote.internal.protocol.Request
-import org.gradle.messaging.remote.internal.protocol.ConsumerUnavailable
-
-class UnicastSendProtocolTest extends Specification {
-    final ProtocolContext<Message> context = Mock()
-    final UnicastSendProtocol protocol = new UnicastSendProtocol()
-
-    def setup() {
-        protocol.start(context)
-    }
-
-    def "queues outgoing messages until a consumer is available"() {
-        when:
-        protocol.handleOutgoing(new Request("channel", "message1"))
-        protocol.handleOutgoing(new Request("channel", "message2"))
-
-        then:
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-
-        then:
-        1 * context.dispatchOutgoing(new Request("id", "message1"))
-        1 * context.dispatchOutgoing(new Request("id", "message2"))
-        0 * context._
-    }
-
-    def "forwards messages when a consumer is available"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-
-        when:
-        protocol.handleOutgoing(new Request("channel", "message"))
-
-        then:
-        1 * context.dispatchOutgoing(new Request("id", "message"))
-    }
-
-    def "stop waits until a consumer is available and messages dispatched"() {
-        given:
-        protocol.handleOutgoing(new Request("channel", "message"))
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.stopLater()
-        0 * context._
-
-        when:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channnel"))
-
-        then:
-        1 * context.dispatchOutgoing(new Request("id", "message"))
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "stops immediately when no messages have been dispatched"() {
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "stops immediately when all messages have been dispatched"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-        protocol.handleOutgoing(new Request("channel", "message"))
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "discards messages after consumer becomes unavailable"() {
-        given:
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-        protocol.handleIncoming(new ConsumerUnavailable("id"))
-
-        when:
-        protocol.handleOutgoing(new Request("channel", "message"))
-
-        then:
-        0 * context._
-
-        when:
-        protocol.stopRequested()
-
-        then:
-        1 * context.stopped()
-        0 * context._
-    }
-
-    def "stop ignores consumer unavailable when everything dispatched"() {
-        given:
-        protocol.handleOutgoing(new Request("channel", "message"))
-        protocol.handleIncoming(new ConsumerAvailable("id", "display", "channel"))
-
-        when:
-        protocol.handleIncoming(new ConsumerUnavailable("id"))
-        protocol.stopRequested()
-
-        then:
-        1 * context.stopped()
-        0 * context._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
deleted file mode 100644
index cd35ce5..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.messaging.remote.internal.inet
-
-import java.util.concurrent.atomic.AtomicInteger
-import org.gradle.api.Action
-import org.gradle.api.logging.Logging
-import org.gradle.messaging.remote.internal.DefaultMessageSerializer
-import org.gradle.util.ConcurrentSpecification
-import org.gradle.util.UUIDGenerator
-import spock.lang.Ignore
-import spock.lang.Timeout
-import static java.util.Collections.synchronizedList
-
-class TcpConnectorConcurrencyTest extends ConcurrentSpecification {
-
-    final static LOGGER = Logging.getLogger(TcpConnectorConcurrencyTest)
-
-    //sharing serializer adds extra flavor...
-    final serializer = new DefaultMessageSerializer<Object>(getClass().classLoader)
-    final outgoingConnector = new TcpOutgoingConnector<Object>(serializer)
-    final incomingConnector = new TcpIncomingConnector<Object>(executorFactory, serializer, new InetAddressFactory(), new UUIDGenerator())
-
-    @Timeout(60)
-    @Ignore
-    //TODO SF exposes concurrency issue
-    def "can dispatch from multiple threads"() {
-        def number = new AtomicInteger(1)
-        def threads = 20
-        def messages = synchronizedList([])
-
-        Action action = new Action() {
-            void execute(event) {
-                while (true) {
-                    def message = event.connection.receive()
-                    LOGGER.debug("*** received: $message")
-                    messages << message
-                    if (messages.size() == threads || message == null) {
-                        break;
-                    }
-                }
-            }
-        }
-
-        def address = incomingConnector.accept(action, false)
-        def connection = outgoingConnector.connect(address)
-
-        when:
-        def all = []
-        threads.times {
-            all << start {
-                //exceptions carry lots of information so serialization/deserialization is slower
-                //and hence better chance of reproducing the concurrency bugs
-                def message = new RuntimeException("Message #" + number.getAndIncrement())
-                connection.dispatch(message)
-                LOGGER.debug("*** dispatched: $message")
-            }
-        }
-
-        all*.completed()
-
-        then:
-        //let's give some time for the messages to arrive to the sink
-        poll(20) {
-            messages.size() == threads
-            messages.each { it.toString().contains("Message #") }
-        }
-
-        cleanup:
-        incomingConnector.requestStop()
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
deleted file mode 100644
index 1105f66..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.messaging.remote.internal.inet
-
-import org.gradle.api.Action
-import org.gradle.messaging.remote.internal.ConnectException
-import org.gradle.messaging.remote.internal.DefaultMessageSerializer
-import org.gradle.util.ConcurrentSpecification
-import org.gradle.util.UUIDGenerator
-
-class TcpConnectorTest extends ConcurrentSpecification {
-    final def serializer = new DefaultMessageSerializer<String>(getClass().classLoader)
-    final def idGenerator = new UUIDGenerator()
-    final def addressFactory = new InetAddressFactory()
-    final def outgoingConnector = new TcpOutgoingConnector<String>(serializer)
-    final def incomingConnector = new TcpIncomingConnector<String>(executorFactory, serializer, addressFactory, idGenerator)
-
-    def "client can connect to server"() {
-        Action action = Mock()
-
-        when:
-        def address = incomingConnector.accept(action, false)
-        def connection = outgoingConnector.connect(address)
-
-        then:
-        connection != null
-
-        cleanup:
-        incomingConnector.requestStop()
-    }
-
-    def "client can connect to server using remote addresses"() {
-        Action action = Mock()
-
-        when:
-        def address = incomingConnector.accept(action, true)
-        def connection = outgoingConnector.connect(address)
-
-        then:
-        connection != null
-
-        cleanup:
-        incomingConnector.requestStop()
-    }
-
-    def "server executes action when incoming connection received"() {
-        def connectionReceived = startsAsyncAction()
-        Action action = Mock()
-
-        when:
-        connectionReceived.started {
-            def address = incomingConnector.accept(action, false)
-            outgoingConnector.connect(address)
-        }
-
-        then:
-        1 * action.execute(!null) >> { connectionReceived.done() }
-
-        cleanup:
-        incomingConnector.requestStop()
-    }
-
-    def "client throws exception when cannot connect to server"() {
-        def address = new MultiChoiceAddress("address", 12345, [InetAddress.getByName("localhost")])
-
-        when:
-        outgoingConnector.connect(address)
-
-        then:
-        ConnectException e = thrown()
-        e.message.startsWith "Could not connect to server ${address}."
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
deleted file mode 100644
index 7cbb932..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol
-
-import org.gradle.messaging.remote.internal.inet.MultiChoiceAddress
-import org.gradle.util.UUIDGenerator
-import spock.lang.Shared
-import spock.lang.Specification
-import org.gradle.messaging.remote.internal.MessageOriginator
-import org.gradle.messaging.remote.internal.inet.SocketInetAddress
-
-class DiscoveryProcotolSerializerTest extends Specification {
-    final DiscoveryProtocolSerializer serializer = new DiscoveryProtocolSerializer()
-    @Shared def uuidGenerator = new UUIDGenerator()
-    @Shared MessageOriginator messageOriginator = new MessageOriginator(uuidGenerator.generateId(), "source display name")
-    @Shared InetAddress address = InetAddress.getByName(null)
-    final InetAddress receivedAddress = Mock()
-
-    def "writes and reads message types"() {
-        when:
-        def result = send(original)
-
-        then:
-        result == original
-
-        where:
-        original << [
-                new LookupRequest(messageOriginator, "group", "channel"),
-                new ChannelUnavailable(messageOriginator, "group", "channel", new MultiChoiceAddress(UUID.randomUUID(), 8091, [address]))
-        ]
-    }
-
-    def "mixes in remote address to received ChannelAvailable message"() {
-        def originatorId = UUID.randomUUID()
-        def original = new ChannelAvailable(messageOriginator, "group", "channel", new MultiChoiceAddress(originatorId, 8091, [address]))
-        def expected = new ChannelAvailable(messageOriginator, "group", "channel", new MultiChoiceAddress(originatorId, 8091, [receivedAddress, address]))
-
-        when:
-        def result = send(original)
-
-        then:
-        result == expected
-    }
-
-    def "can read message for unknown protocol version"() {
-        expect:
-        def result = send { outstr ->
-            outstr.writeByte(90)
-        }
-        result instanceof UnknownMessage
-        result.toString() == "unknown protocol version 90"
-    }
-
-    def "can read unknown message type"() {
-        expect:
-        def result = send { outstr ->
-            outstr.writeByte(DiscoveryProtocolSerializer.PROTOCOL_VERSION);
-            outstr.writeByte(90)
-        }
-        result instanceof UnknownMessage
-        result.toString() == "unknown message type 90"
-    }
-
-    def send(Closure cl) {
-        def bytesOut = new ByteArrayOutputStream()
-        def outstr = new DataOutputStream(bytesOut)
-        cl.call(outstr)
-        outstr.close()
-
-        def bytesIn = new ByteArrayInputStream(bytesOut.toByteArray())
-        return serializer.read(new DataInputStream(bytesIn), null, new SocketInetAddress(receivedAddress, 9122))
-    }
-
-    def send(DiscoveryMessage message) {
-        def bytesOut = new ByteArrayOutputStream()
-        def outstr = new DataOutputStream(bytesOut)
-        serializer.write(message, outstr)
-        outstr.close()
-
-        def bytesIn = new ByteArrayInputStream(bytesOut.toByteArray())
-        return serializer.read(new DataInputStream(bytesIn), null, new SocketInetAddress(receivedAddress, 9122))
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy
new file mode 100644
index 0000000..09d72c6
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleSpec.groovy
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2010 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 org.gradle.process.internal;
+
+
+import org.gradle.process.ExecResult
+import org.gradle.process.internal.streams.StreamsHandler
+import org.gradle.util.GUtil
+import org.gradle.util.Jvm
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Ignore
+import spock.lang.Specification
+import spock.lang.Timeout
+
+import java.util.concurrent.Callable
+
+/**
+ * @author Tom Eyckmans, Szczepan Faber
+ */
+ at Timeout(60)
+class DefaultExecHandleSpec extends Specification {
+    @Rule final TemporaryFolder tmpDir = new TemporaryFolder();
+
+    void "forks process"() {
+        given:
+        def out = new ByteArrayOutputStream();
+        def err = new ByteArrayOutputStream();
+
+        def execHandle = handle()
+                .args(args(TestApp.class, "arg1", "arg2"))
+                .setStandardOutput(out)
+                .setErrorOutput(err)
+                .build();
+
+        when:
+        def result = execHandle.start().waitForFinish();
+
+        then:
+        execHandle.state == ExecHandleState.SUCCEEDED
+        result.exitValue == 0
+        out.toString() == "output args: [arg1, arg2]"
+        err.toString() == "error args: [arg1, arg2]"
+        result.assertNormalExitValue()
+    }
+
+    void "waiting for process returns quickly if process already completed"() {
+        given:
+        def execHandle = handle()
+                .args(args(TestApp.class))
+                .build();
+
+        def handle = execHandle.start()
+
+        when:
+        handle.waitForFinish();
+        handle.waitForFinish();
+
+        then:
+        execHandle.state == ExecHandleState.SUCCEEDED
+    }
+
+    void "understands when application exits with non-zero"() {
+        given:
+        def execHandle = handle().args(args(BrokenApp.class)).build();
+
+        when:
+        def result = execHandle.start().waitForFinish();
+
+        then:
+        execHandle.state == ExecHandleState.FAILED
+        result.exitValue == 72
+
+        when:
+        result.assertNormalExitValue();
+
+        then:
+        def e = thrown(ExecException)
+        e.message.contains "finished with non-zero exit value 72"
+    }
+
+    void "start fails when process cannot be started"() {
+        def execHandle = handle().setDisplayName("awesome").executable("no_such_command").build();
+
+        when:
+        execHandle.start();
+
+        then:
+        def e = thrown(ExecException)
+        e.message == "A problem occurred starting process 'awesome'"
+    }
+
+    void "aborts process"() {
+        def execHandle = handle().args(args(SlowApp.class)).build();
+
+        when:
+        execHandle.start();
+        execHandle.abort();
+        def result = execHandle.waitForFinish();
+
+        then:
+        execHandle.state == ExecHandleState.ABORTED
+        result.exitValue != 0
+    }
+
+    void "clients can listen to notifications"() {
+        ExecHandleListener listener = Mock()
+        def execHandle = handle().listener(listener).args(args(TestApp.class)).build();
+
+        when:
+        execHandle.start();
+        execHandle.waitForFinish()
+
+        then:
+        1 * listener.executionStarted(execHandle)
+        1 * listener.executionFinished(execHandle, _ as ExecResult)
+        0 * listener._
+    }
+
+    void "forks daemon and aborts it"() {
+        def output = new ByteArrayOutputStream()
+        def execHandle = handle().setDaemon(true).setStandardOutput(output).args(args(SlowDaemonApp.class)).build();
+
+        when:
+        execHandle.start();
+        execHandle.waitForFinish();
+
+        then:
+        output.toString().contains "I'm the daemon"
+        execHandle.state == ExecHandleState.DETACHED
+
+        cleanup:
+        execHandle.abort()
+    }
+
+    @Ignore //TODO SF not yet implemented
+    void "aborts daemon"() {
+        def output = new ByteArrayOutputStream()
+        def execHandle = handle().setDaemon(true).setStandardOutput(output).args(args(SlowDaemonApp.class)).build();
+
+        when:
+        execHandle.start();
+        execHandle.waitForFinish();
+
+        then:
+        execHandle.state == ExecHandleState.DETACHED
+
+        when:
+        execHandle.abort()
+        def result = execHandle.waitForFinish()
+
+        then:
+        execHandle.state == ExecHandleState.ABORTED
+        result.exitValue != 0
+    }
+
+    void "detaching does not trigger 'finished' notification"() {
+        def out = new ByteArrayOutputStream()
+        ExecHandleListener listener = Mock()
+        def execHandle = handle().setDaemon(true).listener(listener).setStandardOutput(out).args(args(SlowDaemonApp.class)).build();
+
+        when:
+        execHandle.start();
+        execHandle.waitForFinish();
+
+        then:
+        out.toString().contains "I'm the daemon"
+        1 * listener.executionStarted(execHandle)
+        0 * listener.executionFinished(_, _)
+
+        cleanup:
+        execHandle.abort()
+    }
+
+    @Ignore //TODO SF not yet implemented
+    void "can detach from long daemon and then wait for finish"() {
+        def out = new ByteArrayOutputStream()
+        def execHandle = handle().setStandardOutput(out).args(args(SlowDaemonApp.class, "200")).build();
+
+        when:
+        execHandle.start();
+        execHandle.waitForFinish();
+
+        then:
+        out.toString().contains "I'm the daemon"
+
+        when:
+        execHandle.waitForFinish()
+
+        then:
+        execHandle.state == ExecHandleState.SUCCEEDED
+    }
+
+    @Ignore //TODO SF not yet implemented
+    void "can detach from fast app then wait for finish"() {
+        def out = new ByteArrayOutputStream()
+        def execHandle = handle().setStandardOutput(out).args(args(TestApp.class)).build();
+
+        when:
+        execHandle.start();
+        execHandle.waitForFinish();
+        execHandle.waitForFinish()
+
+        then:
+        execHandle.state == ExecHandleState.SUCCEEDED
+    }
+
+    @Ignore
+    //TODO SF. I have a feeling it is not really testable cleanly.
+    void "detach detects when process did not start or died prematurely"() {
+        def execHandle = handle().args(args(BrokenApp.class)).build();
+
+        when:
+        execHandle.start();
+        def detachResult = execHandle.detach();
+
+        then:
+        execHandle.state == ExecHandleState.FAILED
+        detachResult.processCompleted
+        detachResult.execResult.exitValue == 72
+    }
+
+    void "can redirect error stream"() {
+        def out = new ByteArrayOutputStream()
+        def execHandle = handle().args(args(TestApp.class)).setStandardOutput(out).redirectErrorStream().build();
+
+        when:
+        execHandle.start();
+        execHandle.waitForFinish()
+
+        then:
+        ["output args", "error args"].each { out.toString().contains(it) }
+    }
+
+    void "exec handle collaborates with streams handler"() {
+        given:
+        def streamsHandler = Mock(StreamsHandler)
+        def execHandle = handle().args(args(TestApp.class)).setDisplayName("foo proc").streamsHandler(streamsHandler).build();
+
+        when:
+        execHandle.start()
+        def result = execHandle.waitForFinish()
+
+        then:
+        result.rethrowFailure()
+        1 * streamsHandler.connectStreams(_ as Process, "foo proc")
+        1 * streamsHandler.start()
+        1 * streamsHandler.stop()
+        0 * streamsHandler._
+    }
+
+    @Timeout(2)
+    //TODO SF not yet implemented
+    @Ignore
+    void "exec handle can detach with timeout"() {
+        given:
+        def execHandle = handle().args(args(SlowApp.class)).setTimeout(1).build();
+
+        when:
+        execHandle.start()
+        def result = execHandle.waitForFinish()
+
+        then:
+        result
+        //the timeout does not hit
+    }
+
+    //TODO SF not yet implemented
+    @Ignore
+    void "exec handle can wait with timeout"() {
+        given:
+        def execHandle = handle().args(args(SlowApp.class)).setTimeout(1).build();
+
+        when:
+        execHandle.start()
+        def result = execHandle.waitForFinish()
+
+        then:
+        result.exitValue != 0
+        execHandle.state == ExecHandleState.ABORTED
+    }
+
+    static class Prints implements Callable, Serializable {
+
+        String message
+
+        Object call() {
+            return message
+        }
+    }
+
+    @Ignore
+    //TODO SF add coverage (or move somewhere else) - it should over the ibm+windows use case
+    void "consumes input"() {
+        given:
+        def bytes = new ByteArrayOutputStream()
+        def object = new ObjectOutputStream(bytes)
+        object.writeObject(new Prints(message: 'yummie input'))
+        object.flush()
+        object.close()
+        def out = new ByteArrayOutputStream()
+
+        def execHandle = handle().setStandardOutput(out).setStandardInput(new ByteArrayInputStream(bytes.toByteArray())).args(args(InputReadingApp.class)).build();
+
+        when:
+        execHandle.start()
+        def result = execHandle.waitForFinish()
+
+        then:
+        result.rethrowFailure()
+        result.exitValue == 0
+        out.toString().contains('yummie input')
+    }
+
+    private ExecHandleBuilder handle() {
+        new ExecHandleBuilder()
+                .executable(Jvm.current().getJavaExecutable().getAbsolutePath())
+                .setTimeout(20000) //sanity timeout
+                .workingDir(tmpDir.getDir());
+    }
+
+    private List args(Class mainClass, String ... args) {
+        GUtil.flattenElements("-cp", System.getProperty("java.class.path"), mainClass.getName(), args);
+    }
+
+    public static class TestApp {
+        public static void main(String[] args) {
+            System.out.print("output args: " + Arrays.asList(args));
+            System.err.print("error args: " + Arrays.asList(args));
+        }
+    }
+
+    public static class BrokenApp {
+        public static void main(String[] args) {
+            System.exit(72);
+        }
+    }
+
+    public static class SlowApp {
+        public static void main(String[] args) throws InterruptedException {
+            Thread.sleep(10000L);
+        }
+    }
+
+    public static class SlowDaemonApp {
+        public static void main(String[] args) throws InterruptedException {
+            System.out.println("I'm the daemon");
+            System.out.close();
+            System.err.close();
+            int napTime = (args.length == 0) ? 10000L : Integer.valueOf(args[0])
+            Thread.sleep(napTime);
+        }
+    }
+
+    public static class FastDaemonApp {
+        public static void main(String[] args) throws InterruptedException {
+            System.out.println("I'm the daemon");
+            System.out.close();
+            System.err.close();
+        }
+    }
+
+    public static class InputReadingApp {
+        public static void main(String[] args) throws InterruptedException {
+            ObjectInputStream instr = new ObjectInputStream(System.in);
+            Callable<?> main = (Callable<?>) instr.readObject();
+            System.out.println(main.call())
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java
deleted file mode 100644
index 16422ab..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.process.internal;
-
-import org.apache.commons.io.output.CloseShieldOutputStream;
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.process.ExecResult;
-import org.gradle.util.TemporaryFolder;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-
-import static org.junit.Assert.*;
-import static org.hamcrest.Matchers.*;
-
-/**
- * @author Tom Eyckmans
- */
-public class DefaultExecHandleTest {
-    @Rule
-    public final TemporaryFolder tmpDir = new TemporaryFolder();
-
-    @Test
-    public void testCanForkProcess() throws IOException {
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-
-        DefaultExecHandle execHandle = new DefaultExecHandle(
-                "display-name",
-                tmpDir.getDir(),
-                Jvm.current().getJavaExecutable().getAbsolutePath(),
-                Arrays.asList(
-                        "-cp",
-                        System.getProperty("java.class.path"),
-                        TestApp.class.getName(),
-                        "arg1", "arg2"), System.getenv(),
-                out,
-                System.err,
-                new ByteArrayInputStream(new byte[0]),
-                Collections.<ExecHandleListener>emptyList()
-        );
-
-        ExecResult result = execHandle.start().waitForFinish();
-        assertEquals(ExecHandleState.SUCCEEDED, execHandle.getState());
-        assertEquals(0, result.getExitValue());
-        assertEquals("args: [arg1, arg2]", out.toString());
-        result.assertNormalExitValue();
-    }
-
-    @Test
-    public void testProcessCanHaveNonZeroExitCode() throws IOException {
-        DefaultExecHandle execHandle = new DefaultExecHandle(
-                "display-name",
-                tmpDir.getDir(),
-                Jvm.current().getJavaExecutable().getAbsolutePath(),
-                Arrays.asList(
-                        "-cp",
-                        System.getProperty("java.class.path"),
-                        BrokenApp.class.getName()), System.getenv(),
-                new CloseShieldOutputStream(System.out),
-                new CloseShieldOutputStream(System.err),
-                new ByteArrayInputStream(new byte[0]),
-                Collections.<ExecHandleListener>emptyList()
-        );
-
-        ExecResult result = execHandle.start().waitForFinish();
-        assertEquals(ExecHandleState.FAILED, execHandle.getState());
-        assertEquals(72, result.getExitValue());
-        try {
-            result.assertNormalExitValue();
-            fail();
-        } catch (ExecException e) {
-            assertEquals("Display-name finished with (non-zero) exit value 72.", e.getMessage());
-        }
-    }
-
-    @Test
-    public void testThrowsExceptionWhenProcessCannotBeStarted() throws IOException {
-        DefaultExecHandle execHandle = new DefaultExecHandle(
-                "display-name",
-                tmpDir.getDir(),
-                "no_such_command",
-                Arrays.asList("arg"),
-                System.getenv(),
-                System.out,
-                System.err,
-                new ByteArrayInputStream(new byte[0]),
-                Collections.<ExecHandleListener>emptyList()
-        );
-
-        try {
-            execHandle.start();
-            fail();
-        } catch (ExecException e) {
-            assertEquals("A problem occurred starting display-name.", e.getMessage());
-        }
-    }
-
-    @Test
-    public void testAbort() throws IOException {
-        DefaultExecHandle execHandle = new DefaultExecHandle(
-                "display-name",
-                tmpDir.getDir(),
-                Jvm.current().getJavaExecutable().getAbsolutePath(),
-                Arrays.asList(
-                        "-cp",
-                        System.getProperty("java.class.path"),
-                        SlowApp.class.getName()), System.getenv(),
-                new CloseShieldOutputStream(System.out),
-                new CloseShieldOutputStream(System.err),
-                new ByteArrayInputStream(new byte[0]),
-                Collections.<ExecHandleListener>emptyList()
-        );
-
-        execHandle.start();
-        execHandle.abort();
-
-        ExecResult result = execHandle.waitForFinish();
-        assertEquals(ExecHandleState.ABORTED, execHandle.getState());
-        assertThat(result.getExitValue(), not(equalTo(0)));
-    }
-
-    public static class TestApp {
-        public static void main(String[] args) {
-            System.out.print("args: " + Arrays.asList(args));
-        }
-    }
-
-    public static class BrokenApp {
-        public static void main(String[] args) {
-            System.exit(72);
-        }
-    }
-
-    public static class SlowApp {
-        public static void main(String[] args) throws InterruptedException {
-            Thread.sleep(10000L);
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
index 701bf58..8d42177 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
@@ -17,21 +17,22 @@
 package org.gradle.process.internal;
 
 import org.gradle.api.Action;
-import org.gradle.util.ClassPath;
 import org.gradle.api.internal.ClassPathRegistry;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.internal.file.collections.SimpleFileCollection;
 import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.Factory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.MessagingServer;
 import org.gradle.messaging.remote.internal.inet.SocketInetAddress;
 import org.gradle.process.internal.child.IsolatedApplicationClassLoaderWorker;
 import org.gradle.process.internal.launcher.GradleWorkerMain;
-import org.gradle.util.IdGenerator;
 import org.hamcrest.Matchers;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -47,6 +48,7 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 
 @RunWith(JMock.class)
+ at Ignore //TODO SF refactor this bastard to spock
 public class DefaultWorkerProcessFactoryTest {
     private final JUnit4Mockery context = new JUnit4Mockery();
     private final MessagingServer messagingServer = context.mock(MessagingServer.class);
@@ -59,7 +61,7 @@ public class DefaultWorkerProcessFactoryTest {
     @Test
     public void createsAndConfiguresAWorkerProcess() throws Exception {
         final Set<File> processClassPath = Collections.singleton(new File("something.jar"));
-        final ClassPath classPath = context.mock(ClassPath.class);
+        final org.gradle.internal.classpath.ClassPath classPath = context.mock(org.gradle.internal.classpath.ClassPath.class);
 
         context.checking(new Expectations() {{
             one(classPathRegistry).getClassPath("WORKER_PROCESS");
@@ -67,6 +69,11 @@ public class DefaultWorkerProcessFactoryTest {
             allowing(classPath).getAsFiles();
             will(returnValue(processClassPath));
             allowing(fileResolver).resolveLater(".");
+            will(returnValue(new Factory<File>() {
+                public File create() {
+                    return new File(".");
+                }
+            }));
             allowing(fileResolver).resolveFiles(with(Matchers.<Object>notNullValue()));
             will(returnValue(new SimpleFileCollection()));
         }});
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
index 9a192ec..5288956 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
@@ -71,7 +71,7 @@ class DefaultWorkerProcessTest extends MultithreadedTestCase {
                 workerProcess.start()
                 fail()
             } catch (ExecException e) {
-                assertThat(e.message, equalTo("Timeout waiting for $execHandle to connect." as String))
+                assertThat(e.message, equalTo("Timeout after waiting 1.0 seconds for $execHandle to connect." as String))
             }
         }
     }
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
index 1a3e447..a86c2b8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/JvmOptionsTest.groovy
@@ -20,6 +20,8 @@ package org.gradle.process.internal
 
 import org.gradle.api.internal.file.IdentityFileResolver
 import spock.lang.Specification
+import org.gradle.process.JavaForkOptions
+import java.nio.charset.Charset
 
 /**
  * by Szczepan Faber, created at: 2/13/12
@@ -74,6 +76,54 @@ class JvmOptionsTest extends Specification {
     def "provides managed jvm args"() {
         expect:
         parse("-Xms1G -XX:-PrintClassHistogram -Dfile.encoding=UTF-8 -Dfoo.encoding=blah").managedJvmArgs == ["-Xms1G", "-Dfile.encoding=UTF-8"]
+        parse("-Xms1G -XX:-PrintClassHistogram -Xmx2G -Dfoo.encoding=blah").managedJvmArgs == ["-Xms1G", "-Xmx2G", "-Dfile.encoding=UTF-8"]
+    }
+
+    def "file encoding can be set as systemproperty"() {
+        JvmOptions opts = createOpts()
+        when:
+        opts.systemProperty("file.encoding", "ISO-8859-1")
+        then:
+        opts.allJvmArgs.contains("-Dfile.encoding=ISO-8859-1");
+    }
+
+    def "file encoding can be set via defaultFileEncoding property"() {
+        JvmOptions opts = createOpts()
+        when:
+        opts.defaultCharacterEncoding = "ISO-8859-1"
+        then:
+        opts.allJvmArgs.contains("-Dfile.encoding=ISO-8859-1");
+    }
+
+    def "last file encoding definition is used"() {
+        JvmOptions opts = createOpts()
+        when:
+        opts.systemProperty("file.encoding", "ISO-8859-1");
+        opts.defaultCharacterEncoding = "ISO-8859-2"
+        then:
+        !opts.allJvmArgs.contains("-Dfile.encoding=ISO-8859-1");
+        opts.allJvmArgs.contains("-Dfile.encoding=ISO-8859-2");
+
+        when:
+        opts.defaultCharacterEncoding = "ISO-8859-2"
+        opts.systemProperty("file.encoding", "ISO-8859-1")
+        then:
+        !opts.allJvmArgs.contains("-Dfile.encoding=ISO-8859-2")
+        opts.allJvmArgs.contains("-Dfile.encoding=ISO-8859-1");
+    }
+
+    def "file.encoding arg has default value"() {
+        String defaultCharset = Charset.defaultCharset().name()
+        expect:
+        createOpts().allJvmArgs.contains("-Dfile.encoding=${defaultCharset}".toString());
+    }
+
+    def "copyTo respects defaultFileEncoding"(){
+        JavaForkOptions target = Mock(JavaForkOptions)
+        when:
+        parse("-Dfile.encoding=UTF-8 -Dfoo.encoding=blah -Dfile.encoding=UTF-16").copyTo(target)
+        then:
+        1 * target.setDefaultCharacterEncoding( "UTF-16")
     }
 
     private JvmOptions createOpts() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/BootstrapSecurityManagerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/BootstrapSecurityManagerTest.groovy
index 869df3b..48bff24 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/BootstrapSecurityManagerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/BootstrapSecurityManagerTest.groovy
@@ -80,7 +80,7 @@ class BootstrapSecurityManagerTest extends Specification {
 
     def createStdInContent(File... classpath) {
         def out = new ByteArrayOutputStream()
-        def dataOut = new DataOutputStream(out)
+        def dataOut = new DataOutputStream(new EncodedStream.EncodedOutput(out))
         dataOut.writeInt(classpath.length)
         classpath.each { dataOut.writeUTF(it.absolutePath) }
         return new ByteArrayInputStream(out.toByteArray())
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/EncodedStreamTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/EncodedStreamTest.groovy
new file mode 100644
index 0000000..a7e2a9f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/EncodedStreamTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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 org.gradle.process.internal.child
+
+import spock.lang.Specification
+
+class EncodedStreamTest extends Specification {
+    def "can encode and decode an empty stream"() {
+        def outputStream = new ByteArrayOutputStream()
+        def encoder = new EncodedStream.EncodedOutput(outputStream)
+
+        when:
+        encoder.flush()
+
+        then:
+        def inputStream = new ByteArrayInputStream(outputStream.toByteArray())
+        def decoder = new EncodedStream.EncodedInput(inputStream)
+        decoder.read() < 0
+    }
+
+    def "can encode and decode a string"() {
+        def outputStream = new ByteArrayOutputStream()
+        def encoder = new EncodedStream.EncodedOutput(outputStream)
+
+        when:
+        encoder.write("this is some content".bytes)
+        encoder.flush()
+
+        then:
+        def inputStream = new ByteArrayInputStream(outputStream.toByteArray())
+        def decoder = new EncodedStream.EncodedInput(inputStream)
+        def content = decoder.bytes
+        new String(content) == "this is some content"
+    }
+
+    def "can encode and decode binary content"() {
+        def outputStream = new ByteArrayOutputStream()
+        def encoder = new EncodedStream.EncodedOutput(outputStream)
+
+        when:
+        encoder.write(0)
+        encoder.write(127)
+        encoder.write(128)
+        encoder.write(255)
+        encoder.flush()
+
+        then:
+        def inputStream = new ByteArrayInputStream(outputStream.toByteArray())
+        def decoder = new EncodedStream.EncodedInput(inputStream)
+        decoder.read() == 0
+        decoder.read() == 127
+        decoder.read() == 128
+        decoder.read() == 255
+        decoder.read() < 0
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
index 065970d..2491518 100644
--- a/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
@@ -38,7 +38,7 @@ class WorkerProcessClassPathProviderTest extends Specification {
 
     def createsTheWorkerClasspathOnDemand() {
         def cacheDir = tmpDir.dir
-        def classesDir = cacheDir.file('classes')
+        def jarFile = cacheDir.file('gradle-worker.jar')
         DirectoryCacheBuilder cacheBuilder = Mock()
         PersistentCache cache = Mock()
         def initializer = null
@@ -52,13 +52,13 @@ class WorkerProcessClassPathProviderTest extends Specification {
         1 * cacheBuilder.open() >> { initializer.execute(cache); return cache }
         _ * cache.getBaseDir() >> cacheDir
         0 * cache._
-        classpath.asFiles == [classesDir]
-        classesDir.listFiles().length != 0
+        classpath.asFiles == [jarFile]
+        jarFile.file
     }
 
     def reusesTheCachedClasspath() {
         def cacheDir = tmpDir.dir
-        def classesDir = cacheDir.file('classes')
+        def jarFile = cacheDir.file('gradle-worker.jar')
         DirectoryCacheBuilder cacheBuilder = Mock()
         PersistentCache cache = Mock()
 
@@ -71,6 +71,6 @@ class WorkerProcessClassPathProviderTest extends Specification {
         1 * cacheBuilder.open() >> cache
         _ * cache.getBaseDir() >> cacheDir
         0 * cache._
-        classpath.asFiles == [classesDir]
+        classpath.asFiles == [jarFile]
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ClockTest.java b/subprojects/core/src/test/groovy/org/gradle/util/ClockTest.java
index 4ceb9e9..94a88a4 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/ClockTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/util/ClockTest.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.util;
 
+import org.gradle.internal.TimeProvider;
 import org.jmock.Expectations;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
index 9177e11..80a2055 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/CollectionUtilsTest.groovy
@@ -15,52 +15,77 @@
  */
 package org.gradle.util
 
-import org.gradle.api.specs.Spec
-import org.gradle.api.specs.Specs
 import org.gradle.api.Transformer
-
-import spock.lang.*
+import org.gradle.api.specs.Specs
+import spock.lang.Specification
+import static org.gradle.util.CollectionUtils.*
 
 class CollectionUtilsTest extends Specification {
 
     def "list filtering"() {
         given:
         def spec = Specs.convertClosureToSpec { it < 5 }
-        def filter = { Integer[] nums -> CollectionUtils.filter(nums as List, spec) }
-        
+        def filter = { Integer[] nums -> filter(nums as List, spec) }
+
         expect:
-        filter(1,2,3) == [1,2,3]
-        filter(7,8,9) == []
+        filter(1, 2, 3) == [1, 2, 3]
+        filter(7, 8, 9) == []
         filter() == []
-        filter(4,5,6) == [4]
+        filter(4, 5, 6) == [4]
     }
-    
+
     def "list collecting"() {
-        def transformer = new Transformer() { def transform(i) { i * 2 } }
-        def collect = { Integer[] nums -> CollectionUtils.collect(nums as List, transformer) }
-        
+        def transformer = new Transformer() {
+            def transform(i) { i * 2 }
+        }
+        def collect = { Integer[] nums -> collect(nums as List, transformer) }
+
         expect:
-        collect(1,2,3) == [2,4,6]
+        collect(1, 2, 3) == [2, 4, 6]
         collect() == []
     }
 
     def "set filtering"() {
         given:
         def spec = Specs.convertClosureToSpec { it < 5 }
-        def filter = { Integer[] nums -> CollectionUtils.filter(nums as Set, spec) }
-        
+        def filter = { Integer[] nums -> filter(nums as Set, spec) }
+
         expect:
-        filter(1,2,3) == [1,2,3] as Set
-        filter(7,8,9).empty
+        filter(1, 2, 3) == [1, 2, 3] as Set
+        filter(7, 8, 9).empty
         filter().empty
-        filter(4,5,6) == [4] as Set
+        filter(4, 5, 6) == [4] as Set
     }
 
     def toStringList() {
         def list = [42, "string"]
 
         expect:
-        CollectionUtils.toStringList([]) == []
-        CollectionUtils.toStringList(list) == ["42", "string"]
+        toStringList([]) == []
+        toStringList(list) == ["42", "string"]
+    }
+
+    def "list compacting"() {
+        expect:
+        compact([1, null, 2]) == [1, 2]
+        compact([null, 1, 2]) == [1, 2]
+        compact([1, 2, null]) == [1, 2]
+
+        def l = [1, 2, 3]
+        compact(l).is l
+
+    }
+
+    def "list stringize"() {
+        expect:
+        stringize([1,2,3]) == ["1", "2", "3"]
+        stringize([]) == []
     }
+
+    def "stringize"() {
+        expect:
+        stringize(["c", "b", "a"], new TreeSet<String>()) == ["a", "b", "c"] as Set
+    }
+
+
 }
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/CompositeIdGeneratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/CompositeIdGeneratorTest.groovy
deleted file mode 100644
index 273ec38..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/CompositeIdGeneratorTest.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.util
-
-import org.jmock.integration.junit4.JMock
-import org.junit.Test
-import org.junit.runner.RunWith
-import static org.hamcrest.Matchers.*
-import static org.gradle.util.Matchers.*
-import static org.junit.Assert.*
-
- at RunWith(JMock.class)
-class CompositeIdGeneratorTest {
-    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    private final IdGenerator<?> target = context.mock(IdGenerator.class)
-    private final CompositeIdGenerator generator = new CompositeIdGenerator('scope', target)
-
-    @Test
-    public void createsACompositeId() {
-        Object original = 12
-        context.checking {
-            one(target).generateId()
-            will(returnValue(original))
-        }
-        Object id = generator.generateId()
-        assertThat(id, not(sameInstance(original)))
-        assertThat(id, not(equalTo(original)))
-        assertThat(id.toString(), equalTo('scope.12'))
-    }
-
-    @Test
-    public void compositeIdsAreNotEqualWhenOriginalIdsAreDifferent() {
-        context.checking {
-            one(target).generateId()
-            will(returnValue(12))
-            one(target).generateId()
-            will(returnValue('original'))
-        }
-
-        assertThat(generator.generateId(), not(equalTo(generator.generateId())))
-    }
-
-    @Test
-    public void compositeIdsAreNotEqualWhenScopesAreDifferent() {
-        context.checking {
-            exactly(2).of(target).generateId()
-            will(returnValue(12))
-        }
-
-        CompositeIdGenerator other = new CompositeIdGenerator('other', target)
-        assertThat(generator.generateId(), not(equalTo(other.generateId())))
-    }
-
-    @Test
-    public void compositeIdsAreEqualWhenOriginalIdsAreEqual() {
-        context.checking {
-            exactly(2).of(target).generateId()
-            will(returnValue(12))
-        }
-
-        assertThat(generator.generateId(), strictlyEqual(generator.generateId()))
-    }
-}
-
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassPathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassPathTest.groovy
deleted file mode 100644
index af3edb1..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/DefaultClassPathTest.groovy
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.util
-
-import spock.lang.Specification
-
-class DefaultClassPathTest extends Specification {
-    def "can add classpaths together"() {
-        def file1 = new File("a.jar")
-        def file2 = new File("b.jar")
-        def cp1 = new DefaultClassPath(file1)
-        def cp2 = new DefaultClassPath(file2)
-
-        expect:
-        def cp3 = cp1 + cp2
-        cp3.asFiles == [file1, file2]
-    }
-
-    def "add returns lhs when rhs is empty"() {
-        def cp1 = new DefaultClassPath(new File("a.jar"))
-        def cp2 = new DefaultClassPath()
-
-        expect:
-        (cp1 + cp2).is(cp1)
-    }
-
-    def "add returns rhs when lhs is empty"() {
-        def cp1 = new DefaultClassPath()
-        def cp2 = new DefaultClassPath(new File("a.jar"))
-
-        expect:
-        (cp1 + cp2).is(cp2)
-    }
-
-    def "can add collection of files to classpath"() {
-        def file1 = new File("a.jar")
-        def file2 = new File("a.jar")
-        def cp = new DefaultClassPath(file1)
-
-        expect:
-        (cp + [file2]).asFiles == [file1, file2]
-        (cp + []).is(cp)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
index fd45840..d4f778e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
@@ -30,12 +30,12 @@ class FilteringClassLoaderTest {
     private final FilteringClassLoader classLoader = new FilteringClassLoader(FilteringClassLoaderTest.class.getClassLoader())
 
     @Test
-    public void passesThroughSystemClasses() {
+    void passesThroughSystemClasses() {
         assertThat(classLoader.loadClass(String.class.name), sameInstance(String.class))
     }
 
     @Test
-    public void passesThroughSystemPackages() {
+    void passesThroughSystemPackages() {
         assertThat(classLoader.getPackage('java.lang'), notNullValue(Package.class))
         assertThat(classLoader.getPackages(), hasPackage('java.lang'))
     }
@@ -46,14 +46,14 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void passesThroughSystemResources() {
+    void passesThroughSystemResources() {
         assertThat(classLoader.getResource('com/sun/jndi/ldap/jndiprovider.properties'), notNullValue())
         assertThat(classLoader.getResourceAsStream('com/sun/jndi/ldap/jndiprovider.properties'), notNullValue())
         assertTrue(classLoader.getResources('com/sun/jndi/ldap/jndiprovider.properties').hasMoreElements())
     }
 
     @Test
-    public void filtersClasses() {
+    void filtersClasses() {
         classLoader.parent.loadClass(Test.class.name)
 
         try {
@@ -71,7 +71,7 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void filtersPackages() {
+    void filtersPackages() {
         assertThat(classLoader.parent.getPackage('org.junit'), notNullValue())
 
         assertThat(classLoader.getPackage('org.junit'), nullValue())
@@ -79,7 +79,7 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void filtersResources() {
+    void filtersResources() {
         assertThat(classLoader.parent.getResource('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
         assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
         assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), nullValue())
@@ -87,7 +87,7 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void passesThroughClassesInSpecifiedPackages() {
+    void passesThroughClassesInSpecifiedPackages() {
         classLoader.allowPackage('org.junit')
         assertThat(classLoader.loadClass(Test.class.name), sameInstance(Test.class))
         assertThat(classLoader.loadClass(Test.class.name, false), sameInstance(Test.class))
@@ -95,7 +95,7 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void passesThroughSpecifiedClasses() {
+    void passesThroughSpecifiedClasses() {
         classLoader.allowClass(Test.class)
         assertThat(classLoader.loadClass(Test.class.name), sameInstance(Test.class))
         try {
@@ -107,7 +107,24 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void passesThroughSpecifiedPackages() {
+    void filtersSpecifiedClasses() {
+        classLoader.allowPackage("org.junit")
+        classLoader.disallowClass("org.junit.Test")
+
+        canLoadClass(Before)
+        cannotLoadClass(Test)
+    }
+
+    @Test
+    void disallowClassWinsOverAllowClass() {
+        classLoader.allowClass(Test)
+        classLoader.disallowClass(Test.name)
+
+        cannotLoadClass(Test)
+    }
+
+    @Test
+    void passesThroughSpecifiedPackages() {
         assertThat(classLoader.getPackage('org.junit'), nullValue())
         assertThat(classLoader.getPackages(), not(hasPackage('org.junit')))
 
@@ -120,7 +137,7 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void passesThroughResourcesInSpecifiedPackages() {
+    void passesThroughResourcesInSpecifiedPackages() {
         assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
 
         classLoader.allowPackage('org.gradle')
@@ -131,7 +148,7 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void passesThroughResourcesWithSpecifiedPrefix() {
+    void passesThroughResourcesWithSpecifiedPrefix() {
         assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
 
         classLoader.allowResources('org/gradle')
@@ -142,7 +159,7 @@ class FilteringClassLoaderTest {
     }
 
     @Test
-    public void passesThroughSpecifiedResources() {
+    void passesThroughSpecifiedResources() {
         assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
 
         classLoader.allowResource('org/gradle/util/ClassLoaderTest.txt')
@@ -151,4 +168,15 @@ class FilteringClassLoaderTest {
         assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
         assertTrue(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
     }
+
+    void canLoadClass(Class<?> clazz) {
+        assert classLoader.loadClass(clazz.name) == clazz
+    }
+
+    void cannotLoadClass(Class<?> clazz) {
+        try {
+            classLoader.loadClass(clazz.name)
+            fail()
+        } catch (ClassNotFoundException expected) {}
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
index 6e13d04..155c776 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GUtilTest.groovy
@@ -40,6 +40,25 @@ public class GUtilTest extends spock.lang.Specification {
         toCamelCase("-") == ""
     }
 
+    def convertStringToLowerCamelCase() {
+        expect:
+        toLowerCamelCase(null) == null
+        toLowerCamelCase("") == ""
+        toLowerCamelCase("word") == "word"
+        toLowerCamelCase("twoWords") == "twoWords"
+        toLowerCamelCase("TwoWords") == "twoWords"
+        toLowerCamelCase("two-words") == "twoWords"
+        toLowerCamelCase("two.words") == "twoWords"
+        toLowerCamelCase("two words") == "twoWords"
+        toLowerCamelCase("two Words") == "twoWords"
+        toLowerCamelCase("Two Words") == "twoWords"
+        toLowerCamelCase(" Two  \t words\n") == "twoWords"
+        toLowerCamelCase("four or so Words") == "fourOrSoWords"
+        toLowerCamelCase("trailing-") == "trailing"
+        toLowerCamelCase("ABC") == "aBC"
+        toLowerCamelCase(".") == ""
+        toLowerCamelCase("-") == ""
+    }
     
     def convertStringToConstantName() {
         expect:
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
index bc8447d..d037895 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
@@ -19,10 +19,10 @@ package org.gradle.util
 import org.apache.ivy.Ivy
 import org.apache.tools.ant.Main
 import org.codehaus.groovy.runtime.InvokerHelper
-import org.gradle.internal.os.OperatingSystem
-
-import spock.lang.*
 import org.gradle.internal.jvm.Jvm
+import org.gradle.internal.os.OperatingSystem
+import spock.lang.Issue
+import spock.lang.Specification
 
 /**
  * @author Hans Dockter
@@ -30,10 +30,15 @@ import org.gradle.internal.jvm.Jvm
 class GradleVersionTest extends Specification {
     final GradleVersion version = GradleVersion.current()
 
-    def currentVersionHasNonNullVersionAndBuildTime() {
+    def currentVersionHasNonNullVersion() {
         expect:
         version.version
-        version.buildTime
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-1892")
+    def "build time should always print in UTC"() {
+        expect:
+        version.buildTime.endsWith("UTC")
     }
 
     def equalsAndHashCode() {
@@ -42,28 +47,68 @@ class GradleVersionTest extends Specification {
         GradleVersion.version('0.9') != GradleVersion.version('1.0')
     }
 
-    def canConstructPatchVersion() {
-        when:
-        def version = GradleVersion.version('1.0-milestone-2')
-        def patch = GradleVersion.version('1.0-milestone-2a')
-        def nextPatch = GradleVersion.version('1.0-milestone-2b')
-        def nextMilestone = GradleVersion.version('1.0-milestone-3')
+    def canConstructVersionFromString(String version) {
+        expect:
+        def gradleVersion = GradleVersion.version(version)
+        gradleVersion.version == version
+        gradleVersion.toString() == "Gradle ${version}"
 
-        then:
-        version < patch
-        patch < nextPatch
-        nextPatch < nextMilestone
+        where:
+        version << [
+                '1.0',
+                '12.4.5.67',
+                '1.0-milestone-5',
+                '1.0-milestone-5a',
+                '3.2-rc-2',
+        ]
+    }
+
+    def versionsWithTimestampAreConsideredSnapshots(String version) {
+        expect:
+        def gradleVersion = GradleVersion.version(version)
+        gradleVersion.version == version
+        gradleVersion.snapshot
+
+        where:
+        version << [
+                '0.9-20101220110000+1100',
+                '0.9-20101220110000-0800',
+                '1.2-20120501110000'
+        ]
+    }
 
-        patch > version
-        nextPatch > patch
-        nextMilestone > nextPatch
+    def versionsWithoutTimestampAreNotConsideredSnapshots(String version) {
+        expect:
+        !GradleVersion.version(version).snapshot
+
+        where:
+        version << [
+                '0.9-milestone-5',
+                '2.1-rc-1',
+                '1.2',
+                '1.2.1']
     }
 
-    def canConstructSnapshotVersion() {
+    def canOnlyQueryVersionStringForUnrecognizedVersion(String version) {
+        def gradleVersion = GradleVersion.version(version)
+
         expect:
-        GradleVersion.version('0.9-20101220110000+1100').snapshot
-        GradleVersion.version('0.9-20101220110000-0800').snapshot
-        !GradleVersion.version('0.9-rc-1').snapshot
+        gradleVersion.version == version
+        gradleVersion.snapshot
+
+        when:
+        gradleVersion > GradleVersion.version('1.2')
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Cannot compare unrecognized Gradle version '${version}'."
+
+        where:
+        version << [
+                'abc',
+                '3.0-status-5-master',
+                'user-master',
+        ]
     }
 
     def canCompareMajorVersions() {
@@ -127,6 +172,21 @@ class GradleVersionTest extends Specification {
         '1.0'             | '1.0-rc-7'
     }
 
+    def canComparePatchVersion() {
+        expect:
+        GradleVersion.version(a) > GradleVersion.version(b)
+        GradleVersion.version(b) < GradleVersion.version(a)
+        GradleVersion.version(a) == GradleVersion.version(a)
+        GradleVersion.version(b) == GradleVersion.version(b)
+
+        where:
+        a                  | b
+        '1.0-milestone-2a' | '1.0-milestone-2'
+        '1.0-milestone-2b' | '1.0-milestone-2a'
+        '1.0-milestone-3'  | '1.0-milestone-2b'
+        '1.0'              | '1.0-milestone-2b'
+    }
+
     def canCompareSnapshotVersions() {
         expect:
         GradleVersion.version(a) > GradleVersion.version(b)
@@ -139,20 +199,24 @@ class GradleVersionTest extends Specification {
         '0.9-20101220110000+1100' | '0.9-20101220100000+1100'
         '0.9-20101220110000+1000' | '0.9-20101220100000+1100'
         '0.9-20101220110000-0100' | '0.9-20101220100000+0000'
+        '0.9-20101220110000'      | '0.9-20101220100000'
+        '0.9-20101220110000'      | '0.9-20101220110000+0100'
+        '0.9-20101220110000-0100' | '0.9-20101220110000'
         '0.9'                     | '0.9-20101220100000+1000'
+        '0.9'                     | '0.9-20101220100000'
     }
 
-    @Issue("http://issues.gradle.org/browse/GRADLE-1892")
-    def "build time should always print in UTC"() {
-        expect:
-        version.buildTime.endsWith("UTC")
-    }
-    
-    
-    def defaultValuesForGradleVersion() {
+    def canCompareWithNonSymbolicVersions() {
         expect:
-        version.version != null
-        version.buildTime != null
+        GradleVersion.version(a) > GradleVersion.version(b)
+        GradleVersion.version(b) < GradleVersion.version(a)
+        GradleVersion.version(a) == GradleVersion.version(a)
+        GradleVersion.version(b) == GradleVersion.version(b)
+
+        where:
+        a                         | b
+        '0.0-20101220110000+0100' | '1.0'
+        '0.0'                     | '0.9.2'
     }
 
     def prettyPrint() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/LongIdGeneratorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/LongIdGeneratorTest.groovy
deleted file mode 100644
index 87afd67..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/LongIdGeneratorTest.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.util
-
-import java.util.concurrent.CopyOnWriteArraySet
-import org.junit.Test
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
-
-class LongIdGeneratorTest extends MultithreadedTestCase {
-    private final LongIdGenerator generator = new LongIdGenerator()
-
-    @Test
-    public void generatesMonotonicallyIncreasingLongIdsStartingAtOne() {
-        assertThat(generator.generateId(), equalTo(1L))
-        assertThat(generator.generateId(), equalTo(2L))
-        assertThat(generator.generateId(), equalTo(3L))
-    }
-
-    @Test
-    public void generatesUniqueIdsWhenInvokedConcurrently() {
-        Set<Long> ids = new CopyOnWriteArraySet<Long>()
-
-        5.times {
-            start {
-                100.times {
-                    assertTrue(ids.add(generator.generateId()))
-                }
-            }
-        }
-        waitForAll()
-    }
-}
-
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/ServiceLocatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/ServiceLocatorTest.groovy
deleted file mode 100644
index 3f1082e..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/ServiceLocatorTest.groovy
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.util
-
-import spock.lang.Specification
-import org.gradle.internal.service.UnknownServiceException
-
-class ServiceLocatorTest extends Specification {
-    final ClassLoader classLoader = Mock()
-    final ServiceLocator serviceLocator = new ServiceLocator(classLoader)
-
-    def "locates service implementation class using resources of given ClassLoader"() {
-        def serviceFile = stream('org.gradle.ImplClass')
-
-        when:
-        def result = serviceLocator.findServiceImplementationClass(String.class)
-
-        then:
-        result == String
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
-        1 * classLoader.loadClass('org.gradle.ImplClass') >> String
-    }
-
-    def "findServiceImplementationClass() returns null when no service meta data resource available"() {
-        when:
-        def result = serviceLocator.findServiceImplementationClass(String.class)
-
-        then:
-        result == null
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> null
-    }
-
-    def "wraps implementation class load failure"() {
-        def serviceFile = stream('org.gradle.ImplClass')
-        def failure = new ClassNotFoundException()
-
-        when:
-        serviceLocator.findServiceImplementationClass(String.class)
-
-        then:
-        RuntimeException e = thrown()
-        e.message == "Could not load implementation class 'org.gradle.ImplClass' for service 'java.lang.String'."
-        e.cause == failure
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
-        1 * classLoader.loadClass('org.gradle.ImplClass') >> { throw failure }
-    }
-
-    def "ignores comments and whitespace in service meta data resource"() {
-        def serviceFile = stream('''#comment
-
-    org.gradle.ImplClass  
-''')
-
-        when:
-        def result = serviceLocator.findServiceImplementationClass(String.class)
-
-        then:
-        result == String
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
-        1 * classLoader.loadClass('org.gradle.ImplClass') >> String
-    }
-
-    def "findServiceImplementationClass() fails when no implementation class specified in service meta data resource"() {
-        def serviceFile = stream('#empty!')
-
-        when:
-        serviceLocator.findServiceImplementationClass(String.class)
-
-        then:
-        RuntimeException e = thrown()
-        e.message == "Could not determine implementation class for service 'java.lang.String'."
-        e.cause.message == "No implementation class for service 'java.lang.String' specified in resource '${serviceFile}'."
-        1 * classLoader.getResource("META-INF/services/java.lang.String") >> serviceFile
-    }
-
-    def "findServiceImplementationClass() fails when implementation class specified in service meta data resource is not assignable to service type"() {
-        given:
-        implementationDeclared(String, Integer)
-
-        when:
-        serviceLocator.findServiceImplementationClass(String)
-
-        then:
-        RuntimeException e = thrown()
-        e.message == "Could not load implementation class 'java.lang.Integer' for service 'java.lang.String'."
-        e.cause.message == "Implementation class 'java.lang.Integer' is not assignable to service class 'java.lang.String'."
-    }
-
-    def "get() creates an instance of specified service implementation class"() {
-        given:
-        implementationDeclared(CharSequence, String)
-
-        when:
-        def result = serviceLocator.get(CharSequence)
-
-        then:
-        result instanceof String
-    }
-
-    def "get() caches service implementation instances"() {
-        given:
-        implementationDeclared(CharSequence, String)
-
-        when:
-        def obj1 = serviceLocator.get(CharSequence)
-        def obj2 = serviceLocator.get(CharSequence)
-
-        then:
-        obj1.is(obj2)
-    }
-
-    def "get() fails when no meta-data file found for service type"() {
-        when:
-        serviceLocator.get(CharSequence)
-
-        then:
-        UnknownServiceException e = thrown()
-        e.message == "Could not find meta-data resource 'META-INF/services/java.lang.CharSequence' for service 'java.lang.CharSequence'."
-    }
-
-    def "getFactory() returns a factory which creates instances of implementation class"() {
-        given:
-        implementationDeclared(CharSequence, String)
-
-        when:
-        def factory = serviceLocator.getFactory(CharSequence)
-        def obj1 = factory.create()
-        def obj2 = factory.create()
-
-        then:
-        obj1 instanceof String
-        obj2 instanceof String
-        !obj1.is(obj2)
-    }
-
-    def "getFactory() fails when no meta-data file found for service type"() {
-        when:
-        serviceLocator.getFactory(CharSequence)
-
-        then:
-        UnknownServiceException e = thrown()
-        e.message == "Could not find meta-data resource 'META-INF/services/java.lang.CharSequence' for service 'java.lang.CharSequence'."
-    }
-
-    def stream(String contents) {
-        URLStreamHandler handler = Mock()
-        URLConnection connection = Mock()
-        URL url = new URL("custom", "host", 12, "file", handler)
-        _ * handler.openConnection(url) >> connection
-        _ * connection.getInputStream() >> new ByteArrayInputStream(contents.bytes)
-        return url
-    }
-
-    def "newInstance() creates instances of implementation class"() {
-        given:
-        implementationDeclared(CharSequence, String)
-
-        when:
-        def result = serviceLocator.newInstance(CharSequence)
-
-        then:
-        result instanceof String
-    }
-    
-    def implementationDeclared(Class<?> serviceType, Class<?> implementationType) {
-        def serviceFile = stream(implementationType.name)
-        _ * classLoader.getResource("META-INF/services/${serviceType.name}") >> serviceFile
-        _ * classLoader.loadClass(implementationType.name) >> implementationType
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
index 55409ae..ac097c6 100644
--- a/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/util/TextUtilTest.groovy
@@ -17,6 +17,7 @@
 package org.gradle.util
 
 import spock.lang.Specification
+import spock.lang.Unroll
 
 class TextUtilTest extends Specification {
     private static String sep = "separator"
@@ -54,4 +55,18 @@ class TextUtilTest extends Specification {
         "abc\tde" | true
         "abc\nde" | true
     }
+
+    @Unroll
+    def indent() {
+        expect:
+        TextUtil.indent(text, indent) == result
+
+        where:
+        text            | indent | result
+        ""              | ""     | ""
+        "abc"           | "  "   | "  abc"
+        "abc"           | "def"  | "defabc"
+        "abc\ndef\nghi" | " "    | " abc\n def\n ghi"
+        "abc\n\t\n   \nghi" | "X"    | "Xabc\n\t\n   \nXghi"
+    }
 }
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
index 4d35c83..fe49c7d 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
@@ -204,10 +204,10 @@ public abstract class AbstractSpockTaskTest extends Specification {
         task.getOnlyIf().isSatisfiedBy(task)
 
         when:
-        spec.isSatisfiedBy(task) >> false
         task.onlyIf(spec);
 
         then:
+        spec.isSatisfiedBy(task) >> false
         assertFalse(task.getOnlyIf().isSatisfiedBy(task));
     }
 
@@ -290,13 +290,10 @@ public abstract class AbstractSpockTaskTest extends Specification {
         TaskDependency dependencyMock = Mock()
         getTask().dependsOn(dependencyMock)
         dependencyMock.getDependencies(getTask()) >> [task1, task2] 
-
-        when:
         task1.getDidWork() >> false
         task2.getDidWork() >>> [false, true]
 
-
-        then:
+        expect:
         !getTask().dependsOnTaskDidWork()
         getTask().dependsOnTaskDidWork()
     }
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/DefaultFileLockManagerTestHelper.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/DefaultFileLockManagerTestHelper.groovy
new file mode 100644
index 0000000..7d5e399
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/cache/internal/DefaultFileLockManagerTestHelper.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 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 org.gradle.cache.internal
+
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+
+abstract class DefaultFileLockManagerTestHelper {
+
+    private static class AnException extends RuntimeException {}
+
+    static FileAccess createOnDemandFileLock(File file) {
+        new OnDemandFileAccess(file, "test", DefaultFileLockManagerTestHelper.createDefaultFileLockManager())
+    }
+
+    static void unlockUncleanly(File target) {
+        def lock = createDefaultFileLock(target)
+        try {
+            lock.writeFile {
+                throw new AnException()
+            }
+        } catch (AnException e) {
+            lock.close()
+        }
+        lock = createDefaultFileLock(target)
+        try {
+            assert !lock.unlockedCleanly
+        } finally {
+            lock.close()
+        }
+
+    }
+
+    static DefaultFileLockManager createDefaultFileLockManager() {
+        new DefaultFileLockManager(new DefaultProcessMetaDataProvider(new NativeServices().get(ProcessEnvironment)))
+    }
+    
+    static FileLock createDefaultFileLock(File file, FileLockManager.LockMode mode = FileLockManager.LockMode.Exclusive, DefaultFileLockManager manager = createDefaultFileLockManager()) {
+        manager.lock(file, mode, "test lock")        
+    }
+    
+    static File getLockFile(File target) {
+        new File(target.absolutePath + ".lock")
+    }
+
+    static boolean isIntegrityViolated(File file) {
+        try {
+            createOnDemandFileLock(file).readFile { }
+            false
+        } catch (FileIntegrityViolationException e) {
+            true
+        }
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/logging/TestStyledTextOutput.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/logging/TestStyledTextOutput.groovy
new file mode 100644
index 0000000..892fa24
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/logging/TestStyledTextOutput.groovy
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012 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 org.gradle.logging
+
+import org.gradle.logging.StyledTextOutput.Style
+import org.gradle.internal.SystemProperties
+import org.gradle.logging.internal.AbstractStyledTextOutput
+
+class TestStyledTextOutput extends AbstractStyledTextOutput {
+    StringBuilder result = new StringBuilder()
+
+    @Override
+    String toString() {
+        result.toString()
+    }
+
+    TestStyledTextOutput ignoreStyle() {
+        return new TestStyledTextOutput() {
+            @Override protected void doStyleChange(Style style) {
+            }
+        }
+    }
+
+    String getRawValue() {
+        return result.toString()
+    }
+
+    /**
+     * Returns the normalized value of this text output. Normalizes:
+     * - style changes to {style} where _style_ is the lowercase name of the style.
+     * - line endings to \n
+     * - stack traces to {stacktrace}\n
+     */
+    String getValue() {
+        StringBuilder normalised = new StringBuilder()
+
+        String eol = SystemProperties.lineSeparator
+        boolean inStackTrace = false
+        new StringTokenizer(result.toString().replaceAll(eol, '\n'), '\n', true).each { String line ->
+            if (line == '\n') {
+                if (!inStackTrace) {
+                    normalised.append('\n')
+                }
+            } else if (line.matches(/\s+at .+\(.+\)/)) {
+                if (!inStackTrace) {
+                    normalised.append('{stacktrace}\n')
+                }
+                inStackTrace = true
+            } else {
+                inStackTrace = false
+                normalised.append(line)
+            }
+        }
+        return normalised.toString()
+    }
+
+    @Override
+    protected void doStyleChange(Style style) {
+        result.append("{${style.toString().toLowerCase()}}")
+    }
+
+    @Override
+    protected void doAppend(String text) {
+        result.append(text)
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/logging/TestStyledTextOutputFactory.java b/subprojects/core/src/testFixtures/groovy/org/gradle/logging/TestStyledTextOutputFactory.java
new file mode 100644
index 0000000..f791f48
--- /dev/null
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/logging/TestStyledTextOutputFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 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 org.gradle.logging;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.logging.internal.AbstractStyledTextOutputFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestStyledTextOutputFactory extends AbstractStyledTextOutputFactory implements StyledTextOutputFactory {
+    private final List<StyledTextOutput> textOutputs = new ArrayList<StyledTextOutput>();
+
+    public StyledTextOutput create(String logCategory, LogLevel logLevel) {
+        StyledTextOutput textOutput = new TestStyledTextOutput();
+
+        if (logCategory != null) {
+            textOutput.append("{").append(logCategory).append("}");
+        }
+        if (logCategory != null) {
+            textOutput.append("{").append(logLevel.toString()).append("}");
+        }
+
+        textOutputs.add(textOutput);
+        return textOutput;
+    }
+
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        for (StyledTextOutput textOutput: textOutputs) {
+            builder.append(textOutput);
+        }
+        return builder.toString();
+    }
+
+    public void clear() {
+        textOutputs.clear();
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy
index cd11107..502be67 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/tests/fixtures/ConcurrentTestUtil.groovy
@@ -21,8 +21,8 @@ import java.util.concurrent.TimeUnit
 import java.util.concurrent.locks.Condition
 import java.util.concurrent.locks.Lock
 import java.util.concurrent.locks.ReentrantLock
-import org.gradle.messaging.concurrent.ExecutorFactory
-import org.gradle.messaging.concurrent.StoppableExecutor
+import org.gradle.internal.concurrent.ExecutorFactory
+import org.gradle.internal.concurrent.StoppableExecutor
 import org.junit.rules.ExternalResource
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java
index 557221b..6483e6a 100755
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/MultithreadedTestCase.java
@@ -20,8 +20,8 @@ import groovy.lang.Closure;
 import junit.framework.AssertionFailedError;
 import org.codehaus.groovy.runtime.InvokerInvocationException;
 import org.gradle.internal.UncheckedException;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
 import org.hamcrest.Matcher;
 import org.junit.After;
 import org.slf4j.Logger;
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java
index 79ecb00..bfe89e7 100755
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/AvailableCompilers.java
@@ -29,8 +29,8 @@ public class AvailableCompilers {
             compilers.add(findVisualCpp());
             compilers.add(findMinGW());
         } else {
-            compilers.add(findGpp("3"));
-            compilers.add(findGpp("4"));
+            compilers.add(findGpp("3", "/opt/gcc/3.4.6/g++"));
+            compilers.add(findGpp("4", null));
         }
         return compilers;
     }
@@ -71,7 +71,7 @@ public class AvailableCompilers {
         return new UnavailableCompiler("mingw");
     }
 
-    static private CompilerCandidate findGpp(String versionPrefix) {
+    static private CompilerCandidate findGpp(String versionPrefix, String hardcodedFallback) {
         String name = String.format("g++ (%s)", versionPrefix);
         GppVersionDeterminer versionDeterminer = new GppVersionDeterminer();
         for (File candidate : OperatingSystem.current().findAllInPath("g++")) {
@@ -80,6 +80,13 @@ public class AvailableCompilers {
             }
         }
 
+        if (hardcodedFallback != null) {
+            File fallback = new File(hardcodedFallback);
+            if (fallback.isFile()) {
+                return new InstalledCompiler(name, fallback.getParentFile());
+            }
+        }
+
         return new UnavailableCompiler(name);
     }
 
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
index 49863d8..bc7e416 100755
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppIntegrationTestRunner.java
@@ -53,7 +53,12 @@ public class CppIntegrationTestRunner extends AbstractMultiTestRunner {
 
         @Override
         protected boolean isEnabled() {
-            return compiler.isAvailable();
+            return compiler.isAvailable() && canDoNecessaryEnvironmentManipulation();
+        }
+
+        private boolean canDoNecessaryEnvironmentManipulation() {
+            return (compiler.getEnvironmentVars().isEmpty() && compiler.getPathEntries().isEmpty())
+                    || PROCESS_ENVIRONMENT.maybeSetEnvironmentVariable(pathVarName, System.getenv(pathVarName));
         }
 
         @Override
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
index e8c2ddc..6cc5aa7 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/BinariesPlugin.java
@@ -17,7 +17,7 @@ package org.gradle.plugins.binaries;
 
 import org.gradle.api.Plugin;
 import org.gradle.api.internal.FactoryNamedDomainObjectContainer;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.ReflectiveNamedDomainObjectFactory;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.plugins.BasePlugin;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java
index e65b3ac..1052812 100755
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistry.java
@@ -18,7 +18,7 @@ package org.gradle.plugins.binaries.model.internal;
 import com.google.common.base.Joiner;
 import org.gradle.api.Action;
 import org.gradle.api.internal.DefaultNamedDomainObjectSet;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.plugins.binaries.model.Binary;
 import org.gradle.plugins.binaries.model.Compiler;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java
index 9d66019..e4b68e1 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/CppExtension.java
@@ -18,7 +18,7 @@ package org.gradle.plugins.cpp;
 import groovy.lang.Closure;
 import org.gradle.api.NamedDomainObjectContainer;
 import org.gradle.api.internal.FactoryNamedDomainObjectContainer;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.ReflectiveNamedDomainObjectFactory;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.plugins.cpp.internal.DefaultCppSourceSet;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgCollector.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgCollector.java
deleted file mode 100644
index 56e1c2a..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgCollector.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.plugins.cpp.compiler.internal;
-
-public interface ArgCollector {
-    
-    ArgCollector args(Object... args);
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriter.java
deleted file mode 100755
index cd1b861..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriter.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.plugins.cpp.compiler.internal;
-
-import org.gradle.api.Transformer;
-
-import java.io.PrintWriter;
-import java.util.regex.Pattern;
-
-public class ArgWriter implements ArgCollector {
-    private static final Pattern WHITESPACE = Pattern.compile("\\s");
-    private final PrintWriter writer;
-    private final boolean backslashEscape;
-
-    private ArgWriter(PrintWriter writer, boolean backslashEscape) {
-        this.writer = writer;
-        this.backslashEscape = backslashEscape;
-    }
-
-    public static ArgWriter unixStyle(PrintWriter writer) {
-        return new ArgWriter(writer, true);
-    }
-
-    public static Transformer<ArgWriter, PrintWriter> unixStyleFactory() {
-        return new Transformer<ArgWriter, PrintWriter>() {
-            public ArgWriter transform(PrintWriter original) {
-                return unixStyle(original);
-            }
-        };
-    }
-
-    public static ArgWriter windowsStyle(PrintWriter writer) {
-        return new ArgWriter(writer, false);
-    }
-
-    public static Transformer<ArgWriter, PrintWriter> windowsStyleFactory() {
-        return new Transformer<ArgWriter, PrintWriter>() {
-            public ArgWriter transform(PrintWriter original) {
-                return windowsStyle(original);
-            }
-        };
-    }
-
-    /**
-     * Writes a set of args on a single line, escaping and quoting as required.
-     */
-    public ArgWriter args(Object... args) {
-        for (int i = 0; i < args.length; i++) {
-            Object arg = args[i];
-            if (i > 0) {
-                writer.print(' ');
-            }
-            String str = arg.toString();
-            if (backslashEscape) {
-                str = str.replace("\\", "\\\\").replace("\"", "\\\"");
-            }
-            if (WHITESPACE.matcher(str).find()) {
-                writer.print('\"');
-                writer.print(str);
-                writer.print('\"');
-            } else {
-                writer.print(str);
-            }
-        }
-        writer.println();
-        return this;
-    }
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLinCppCompilerArgumentsApplicator.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLinCppCompilerArgumentsApplicator.java
deleted file mode 100644
index 6e5b3d0..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLinCppCompilerArgumentsApplicator.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.plugins.cpp.compiler.internal;
-
-import org.gradle.api.Transformer;
-import org.gradle.plugins.cpp.gpp.GppCompileSpec;
-
-public class CommandLinCppCompilerArgumentsApplicator<T extends GppCompileSpec> implements Transformer<Iterable<String>, T> {
-
-    private final CompileSpecToArguments<T> toArguments;
-
-    public CommandLinCppCompilerArgumentsApplicator(CompileSpecToArguments<T> toArguments) {
-        this.toArguments = toArguments;
-    }
-
-    public Iterable<String> transform(T spec) {
-        ListArgCollector collector = new ListArgCollector();
-        toArguments.collectArguments(spec, collector);
-        return collector.getFlattened();
-    }
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
index cce9294..37a31e9 100755
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompiler.java
@@ -17,7 +17,8 @@
 package org.gradle.plugins.cpp.compiler.internal;
 
 import groovy.lang.Closure;
-import org.gradle.api.Transformer;
+import org.gradle.api.internal.tasks.compile.CompileSpecToArguments;
+import org.gradle.api.internal.tasks.compile.ExecSpecBackedArgCollector;
 import org.gradle.api.internal.tasks.compile.SimpleWorkResult;
 import org.gradle.api.tasks.WorkResult;
 import org.gradle.internal.Factory;
@@ -29,9 +30,9 @@ import java.io.File;
 public class CommandLineCppCompiler<T extends CppCompileSpec> implements CppCompiler<T> {
     private final File executable;
     private final Factory<ExecAction> execActionFactory;
-    private final Transformer<Iterable<String>, T> toArguments;
+    private final CompileSpecToArguments<T> toArguments;
 
-    public CommandLineCppCompiler(File executable, Factory<ExecAction> execActionFactory, Transformer<Iterable<String>, T> toArguments) {
+    public CommandLineCppCompiler(File executable, Factory<ExecAction> execActionFactory, CompileSpecToArguments<T> toArguments) {
         this.executable = executable;
         this.execActionFactory = execActionFactory;
         this.toArguments = toArguments;
@@ -46,7 +47,7 @@ public class CommandLineCppCompiler<T extends CppCompileSpec> implements CppComp
         compiler.executable(executable);
         compiler.workingDir(workDir);
 
-        compiler.args(toArguments.transform(spec));
+        toArguments.collectArguments(spec, new ExecSpecBackedArgCollector(compiler));
 
         // Apply all of the settings
         for (Closure closure : spec.getSettings()) {
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java
index 227d703..1ae0174 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CommandLineCppCompilerArgumentsToOptionFile.java
@@ -18,14 +18,16 @@ package org.gradle.plugins.cpp.compiler.internal;
 
 import org.gradle.api.Transformer;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.tasks.compile.ArgCollector;
+import org.gradle.api.internal.tasks.compile.ArgWriter;
+import org.gradle.api.internal.tasks.compile.CompileSpecToArguments;
 import org.gradle.plugins.cpp.internal.CppCompileSpec;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.Collections;
 
-public class CommandLineCppCompilerArgumentsToOptionFile<T extends CppCompileSpec> implements Transformer<Iterable<String>, T> {
+public class CommandLineCppCompilerArgumentsToOptionFile<T extends CppCompileSpec> implements CompileSpecToArguments<T> {
 
     private final Transformer<ArgWriter, PrintWriter> argWriterFactory;
     private final CompileSpecToArguments<T> toArguments;
@@ -35,7 +37,7 @@ public class CommandLineCppCompilerArgumentsToOptionFile<T extends CppCompileSpe
         this.toArguments = toArguments;
     }
 
-    public Iterable<String> transform(T spec) {
+    public void collectArguments(T spec, ArgCollector collector) {
         File optionsFile = new File(spec.getWorkDir(), "compiler-options.txt");
         try {
             PrintWriter writer = new PrintWriter(optionsFile);
@@ -49,6 +51,6 @@ public class CommandLineCppCompilerArgumentsToOptionFile<T extends CppCompileSpe
             throw new UncheckedIOException(String.format("Could not write compiler options file '%s'.", optionsFile.getAbsolutePath()), e);
         }
 
-        return Collections.singletonList(String.format("@%s", optionsFile.getAbsolutePath()));
+        collector.args(String.format("@%s", optionsFile.getAbsolutePath()));
     }
 }
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CompileSpecToArguments.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CompileSpecToArguments.java
deleted file mode 100644
index d647c3b..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/CompileSpecToArguments.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.plugins.cpp.compiler.internal;
-
-import org.gradle.plugins.cpp.internal.CppCompileSpec;
-
-public interface CompileSpecToArguments<T extends CppCompileSpec> {
-    
-    public void collectArguments(T spec, ArgCollector collector);
-
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ListArgCollector.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ListArgCollector.java
deleted file mode 100644
index a23a342..0000000
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/compiler/internal/ListArgCollector.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.plugins.cpp.compiler.internal;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-public class ListArgCollector implements ArgCollector {
-    
-    private final List<List<String>> groups = new LinkedList<List<String>>();
-
-    public ArgCollector args(Object... args) {
-        List<String> group = new ArrayList<String>(args.length);
-        for (Object arg : args) {
-            group.add(arg.toString());
-        }
-        groups.add(group);
-        
-        return this;
-    }
-    
-    public List<List<String>> getGroups() {
-        return groups;
-    }
-    
-    public List<String> getFlattened() {
-        List<String> flattened = new LinkedList<String>();
-        for (List<String> group : groups) {
-            for (String arg : group) {
-                flattened.add(arg);
-            }
-        }
-
-        return flattened;
-    }
-    
-}
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java
index 0507d54..1d44fff 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompileSpecToArguments.java
@@ -18,8 +18,8 @@ package org.gradle.plugins.cpp.gpp.internal;
 
 import org.gradle.internal.os.OperatingSystem;
 import org.gradle.plugins.binaries.model.LibraryCompileSpec;
-import org.gradle.plugins.cpp.compiler.internal.ArgCollector;
-import org.gradle.plugins.cpp.compiler.internal.CompileSpecToArguments;
+import org.gradle.api.internal.tasks.compile.ArgCollector;
+import org.gradle.api.internal.tasks.compile.CompileSpecToArguments;
 import org.gradle.plugins.cpp.gpp.GppCompileSpec;
 
 import java.io.File;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java
index db54d52..8b5dbf7 100755
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompiler.java
@@ -17,8 +17,7 @@
 package org.gradle.plugins.cpp.gpp.internal;
 
 import org.gradle.internal.Factory;
-import org.gradle.plugins.cpp.compiler.internal.ArgWriter;
-import org.gradle.plugins.cpp.compiler.internal.CommandLinCppCompilerArgumentsApplicator;
+import org.gradle.api.internal.tasks.compile.ArgWriter;
 import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompiler;
 import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerArgumentsToOptionFile;
 import org.gradle.plugins.cpp.gpp.GppCompileSpec;
@@ -32,8 +31,8 @@ public class GppCompiler extends CommandLineCppCompiler<GppCompileSpec> {
         super(executable, execActionFactory, useCommandFile ? viaCommandFile() : withoutCommandFile());
     }
 
-    private static CommandLinCppCompilerArgumentsApplicator<GppCompileSpec> withoutCommandFile() {
-        return new CommandLinCppCompilerArgumentsApplicator<GppCompileSpec>(new GppCompileSpecToArguments());
+    private static GppCompileSpecToArguments withoutCommandFile() {
+        return new GppCompileSpecToArguments();
     }
 
     private static CommandLineCppCompilerArgumentsToOptionFile<GppCompileSpec> viaCommandFile() {
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java
index d0329d5..4763183 100755
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/gpp/internal/GppCompilerAdapter.java
@@ -80,7 +80,7 @@ public class GppCompilerAdapter extends CommandLineCppCompilerAdapter<GppCompile
         try {
             majorVersion = Integer.valueOf(components[0]);
         } catch (NumberFormatException e) {
-            throw new IllegalStateException(String.format("Unable to determine major g++ version from version number {}", version), e);
+            throw new IllegalStateException(String.format("Unable to determine major g++ version from version number %s.", version), e);
         }
 
         return new GppCompiler(getExecutable(), getExecActionFactory(), majorVersion >= 4);
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java
index 6eb3417..91ba77f 100644
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompileSpecToArguments.java
@@ -17,8 +17,8 @@
 package org.gradle.plugins.cpp.msvcpp.internal;
 
 import org.gradle.plugins.binaries.model.LibraryCompileSpec;
-import org.gradle.plugins.cpp.compiler.internal.ArgCollector;
-import org.gradle.plugins.cpp.compiler.internal.CompileSpecToArguments;
+import org.gradle.api.internal.tasks.compile.ArgCollector;
+import org.gradle.api.internal.tasks.compile.CompileSpecToArguments;
 import org.gradle.plugins.cpp.gpp.GppCompileSpec;
 
 import java.io.File;
diff --git a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java
index 1ac1ec8..25767dc 100755
--- a/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java
+++ b/subprojects/cpp/src/main/groovy/org/gradle/plugins/cpp/msvcpp/internal/VisualCppCompiler.java
@@ -17,7 +17,7 @@
 package org.gradle.plugins.cpp.msvcpp.internal;
 
 import org.gradle.internal.Factory;
-import org.gradle.plugins.cpp.compiler.internal.ArgWriter;
+import org.gradle.api.internal.tasks.compile.ArgWriter;
 import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompiler;
 import org.gradle.plugins.cpp.compiler.internal.CommandLineCppCompilerArgumentsToOptionFile;
 import org.gradle.plugins.cpp.gpp.GppCompileSpec;
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy
index 6204e77..16d0a45 100644
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy
+++ b/subprojects/cpp/src/test/groovy/org/gradle/plugins/binaries/model/internal/DefaultCompilerRegistryTest.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.plugins.binaries.model.internal
 
-import org.gradle.api.internal.DirectInstantiator
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.api.internal.tasks.compile.Compiler
 import org.gradle.plugins.binaries.model.Binary
 import spock.lang.Specification
diff --git a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriterSpec.groovy b/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriterSpec.groovy
deleted file mode 100755
index 59d78fb..0000000
--- a/subprojects/cpp/src/test/groovy/org/gradle/plugins/cpp/compiler/internal/ArgWriterSpec.groovy
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.plugins.cpp.compiler.internal
-
-import spock.lang.Specification
-import static org.gradle.util.TextUtil.*
-
-class ArgWriterSpec extends Specification {
-    final StringWriter writer = new StringWriter()
-    final PrintWriter printWriter = new PrintWriter(writer, true)
-    final ArgWriter argWriter = ArgWriter.unixStyle(printWriter)
-
-    def "writes single argument to line"() {
-        when:
-        argWriter.args("-nologo")
-
-        then:
-        writer.toString() == toPlatformLineSeparators("-nologo\n")
-    }
-
-    def "writes multiple arguments to line"() {
-        when:
-        argWriter.args("-I", "some/dir")
-
-        then:
-        writer.toString() == toPlatformLineSeparators("-I some/dir\n")
-    }
-
-    def "quotes argument with whitespace"() {
-        when:
-        argWriter.args("ab c", "d e f")
-
-        then:
-        writer.toString() == toPlatformLineSeparators('"ab c" "d e f"\n')
-    }
-
-    def "escapes double quotes in argument"() {
-        when:
-        argWriter.args('"abc"', 'a" bc')
-
-        then:
-        writer.toString() == toPlatformLineSeparators('\\"abc\\" "a\\" bc"\n')
-    }
-
-    def "escapes backslash in argument"() {
-        when:
-        argWriter.args('a\\b', 'a \\ bc')
-
-        then:
-        writer.toString() == toPlatformLineSeparators('a\\\\b "a \\\\ bc"\n')
-    }
-
-    def "does not escape characters in windows style"() {
-        def argWriter = ArgWriter.windowsStyle(printWriter)
-
-        when:
-        argWriter.args('a\\b', 'a "\\" bc')
-
-        then:
-        writer.toString() == toPlatformLineSeparators('a\\b "a "\\" bc"\n')
-    }
-}
diff --git a/subprojects/docs/docs.gradle b/subprojects/docs/docs.gradle
index 83d9557..25c660a 100755
--- a/subprojects/docs/docs.gradle
+++ b/subprojects/docs/docs.gradle
@@ -20,7 +20,6 @@ import org.gradle.build.docs.AssembleSamplesDocTask
 import org.gradle.build.docs.Docbook2Xhtml
 import org.gradle.build.docs.dsl.docbook.AssembleDslDocTask
 import org.gradle.build.docs.dsl.ExtractDslMetaDataTask
-import org.gradle.build.GenerateReleasesXml
 import org.gradle.internal.os.OperatingSystem
 
 apply plugin: 'base'
@@ -75,6 +74,18 @@ tasks.withType(UserGuideTransformTask) {
     dependsOn samples, dslDocbook
     snippetsDir = samples.snippetsDir
     linksFile = dslDocbook.linksFile
+    websiteUrl = 'http://www.gradle.org'
+    
+    if (name in ["pdfUserguideDocbook", "userguideFragmentSrc"]) {
+        // These will only be valid for releases, but that's ok
+        javadocUrl = "http://www.gradle.org/doc/${->version}/javadoc"
+        groovydocUrl = "http://www.gradle.org/doc/${->version}/groovydoc"
+        dsldocUrl = "http://www.gradle.org/doc/${->version}/dsl"
+    } else {
+        javadocUrl = '../javadoc'
+        groovydocUrl = '../groovydoc'
+        dsldocUrl = '../dsl'
+    }
 }
 tasks.withType(AssembleDslDocTask) {
     classDocbookDir = dslSrcDir
@@ -140,10 +151,7 @@ task dslDocbook(type: AssembleDslDocTask, dependsOn: [dslMetaData]) {
 task dslStandaloneDocbook(type: UserGuideTransformTask, dependsOn: [dslDocbook]) {
     sourceFile = dslDocbook.destFile
     destFile = new File(docbookSrc, 'dsl-standalone.xml')
-    javadocUrl = '../javadoc'
-    groovydocUrl = '../groovydoc'
     dsldocUrl = '.'
-    websiteUrl = 'http://www.gradle.org'
 }
 
 task dslHtml(type: Docbook2Xhtml) {
@@ -160,17 +168,11 @@ task dslHtml(type: Docbook2Xhtml) {
 // This is used in the distribution and for the online version
 task userguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook]) {
     destFile = new File(docbookSrc, 'userguide.xml')
-    javadocUrl = '../javadoc'
-    groovydocUrl = '../groovydoc'
-    dsldocUrl = '../dsl'
 }
 
 // This is used for the PDF, where we need absolute links to the javadoc etc.
 task pdfUserguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook]) {
     destFile = new File(docbookSrc, 'remoteUserguide.xml')
-    javadocUrl = project.version.javadocUrl
-    groovydocUrl = project.version.groovydocUrl
-    dsldocUrl = project.version.dsldocUrl
 }
 
 configure([userguideDocbook, pdfUserguideDocbook]) {
@@ -288,7 +290,7 @@ task groovydoc(type: Groovydoc, dependsOn: configureGroovydoc) {
         if (it.hasTask(groovydoc)) {
             def systemCharset = java.nio.charset.Charset.defaultCharset().name()
             if (systemCharset != "UTF-8") {
-                if (isReleaseBuild()) {
+                if (!isSnapshot) {
                     throw new InvalidUserDataException("Cannot run $groovydoc.name task unless system charset is UTF-8 (it's $systemCharset, set -Dfile.encoding=UTF-8) in GRADLE_OPTS")
                 } else {
                     logger.warn("Groovydoc will be generated in this build, but the default character encoding is '$systemCharset'. It should be 'UTF-8'. This is ok for a non release build.")
@@ -309,10 +311,6 @@ task userguideFragmentSrc(type: UserGuideTransformTask, dependsOn: [userguideSty
     tags << 'standalone'
     sourceFile = new File(userguideSrcDir, 'installation.xml')
     destFile = new File(docbookSrc, 'installation.xml')
-    javadocUrl = project.version.javadocUrl
-    groovydocUrl = project.version.groovydocUrl
-    dsldocUrl = project.version.dsldocUrl
-    websiteUrl = 'http://www.gradle.org'
 }
 
 task distDocs(type: Docbook2Xhtml) {
@@ -341,7 +339,7 @@ import org.gradle.plugins.pegdown.PegDown
 import org.gradle.plugins.jsoup.Jsoup
 
 task editReleaseNotes() << {
-    new java.awt.Desktop().edit(file("src/docs/release/notes.md"))
+    Class.forName("java.awt.Desktop").newInstance().edit(file("src/docs/release/notes.md"))
 }
 
 task releaseNotesMarkdown(type: PegDown) {
@@ -370,7 +368,7 @@ task releaseNotes(type: Copy) {
 }
 
 task viewReleaseNotes(dependsOn: releaseNotes) << {
-    new java.awt.Desktop().browse(new File(releaseNotes.destinationDir, releaseNotes.fileName).toURI())
+    Class.forName("java.awt.Desktop").newInstance().browse(new File(releaseNotes.destinationDir, releaseNotes.fileName).toURI())
 }
 
 class Xhtml2Pdf extends DefaultTask {
diff --git a/subprojects/docs/release-notes-transform.gradle b/subprojects/docs/release-notes-transform.gradle
index 2dcc005..96cb33f 100644
--- a/subprojects/docs/release-notes-transform.gradle
+++ b/subprojects/docs/release-notes-transform.gradle
@@ -1,5 +1,17 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath ('com.uwyn:jhighlight:1.0') {
+            exclude module: "servlet-api"
+        }
+    }
+}
+
 import org.jsoup.nodes.Element
 import org.jsoup.select.Elements
+import com.uwyn.jhighlight.renderer.XhtmlRendererFactory
 
 decorateReleaseNotes {
     
@@ -27,7 +39,7 @@ decorateReleaseNotes {
             replace("@regular-font-base64@", fontRegularFile.bytes.encodeBase64().toString()).
             replace("@bold-font-base64@", fontBoldFile.bytes.encodeBase64().toString())
         
-        head().append("<style/>").children().last().text(styleText)
+        head().append("<style>p{}</style>").children().last().childNode(0).attr("data", styleText)
         
         head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", jqueryFile.text)
         head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", scriptFile.text)
@@ -154,4 +166,38 @@ decorateReleaseNotes {
         def footer = body().append("<section class='footer'/>").children().last()
         footer.html("Gradle @version@ Release Notes<br />— <a href='http://gradle.org'>www.gradle.org</a> —")
     }
+    
+    // Syntax highlighting
+    transform {
+        body().select("code").each { code ->
+            def parent = code.parent()
+            if (parent.tagName() == "pre") {
+                def text = code.text()
+                def input = new ByteArrayInputStream(code.text().getBytes("utf-8"))
+                def renderer = XhtmlRendererFactory.getRenderer("groovy")
+                def out = new ByteArrayOutputStream()
+                renderer.highlight("test", input, out, "utf-8", true)
+                code.html(new String(out.toByteArray(), "utf-8"))
+                code.select("br").remove()
+                code.childNodes().findAll { it.nodeName().equals("#comment") }*.remove()
+                code.html(code.html().trim())
+                parent.addClass("code")
+            }
+        }
+    }
+    
+    // Terminal styling
+    transform {
+        body().select("tt").each { tt ->
+            def parent = tt.parent()
+            if (parent.tagName() == "pre") {
+                tt.select("br").remove()
+                tt.childNodes().findAll { it.nodeName().equals("#comment") }*.remove()
+                tt.html(tt.html().trim())
+                parent.addClass("tt")
+            }
+        }
+    }
+    
+    
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/css/dsl.css b/subprojects/docs/src/docs/css/dsl.css
index bc7bbc6..6ddf2e6 100644
--- a/subprojects/docs/src/docs/css/dsl.css
+++ b/subprojects/docs/src/docs/css/dsl.css
@@ -1,4 +1,8 @@
 
+.caution {
+    font-weight: bold;
+}
+
 .sidebar {
     margin-top: 2em;
     margin-left: 1.8em;
diff --git a/subprojects/docs/src/docs/dsl/dsl.xml b/subprojects/docs/src/docs/dsl/dsl.xml
index 2f54fc5..881b3f4 100644
--- a/subprojects/docs/src/docs/dsl/dsl.xml
+++ b/subprojects/docs/src/docs/dsl/dsl.xml
@@ -176,6 +176,9 @@
             <tr>
                 <td>org.gradle.api.artifacts.dsl.ArtifactHandler</td>
             </tr>
+            <tr>
+                <td>org.gradle.api.tasks.testing.logging.TestLoggingContainer</td>
+            </tr>
         </table>
     </section>
 
@@ -376,4 +379,35 @@
             </tr>
         </table>
     </section>
+
+    <section id="dsl-element-types">
+        <title>Backwards compatibility across Gradle versions</title>
+        <para>Most DSL elements documented here are considered <firstterm>public</firstterm> elements. This means that they are fully supported
+            and will remain unchanged until at least the next major version of Gradle. And in most cases, a feature will be supported well
+            beyond the next major version.
+            By <firstterm>major version</firstterm>, we mean a change to the first part of the version number. So, if a public feature is available
+            in Gradle 1.2, it will continue to be available in Gradle 1.3, Gradle 1.4, and so on until at least Gradle 2.0.
+        </para>
+        <para>Some elements of the DSL are marked as <firstterm>deprecated</firstterm>. This means that the element is not longer
+            intended to be used, and will be removed at some point in the future. In most cases, there is a replacement for the deprecated element,
+            and this will be described in the documentation.
+        </para>
+        <para>
+            A deprecated element will not be removed before the next major version of Gradle. You can continue to use the element
+            until then. However, you will receive a deprecation warning when you run a build that uses the element, to remind you that the
+            element is to be removed.
+            We recommend that you change your code so that it no longer uses the deprecated element and uses the appropriate replacement instead.
+        </para>
+        <para>Some elements of the DSL are marked as <firstterm>experimental</firstterm>. This means that the element is a work-in-progress,
+            and may require some changes before it is ready to become public. These changes are based on feedback as these features are used in
+            real builds. Please feel free to try an experimental element and give us your feedback, but please be aware that it may change in
+            future versions of Gradle. Note that this includes minor versions.
+        </para>
+        <para>Finally, there are also some <firstterm>internal</firstterm> undocumented features present in Gradle.
+            These are intended to be used in Gradle itself, and may change at any time.
+            Internal features are not documented in this DSL reference, the user guide, or in the javadoc and groovydoc API documentation.
+            If a feature is not documented, then it is likely an internal feature, and we recommend that you avoid using it.
+        </para>
+    </section>
+
 </book>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml
index fd28dd6..8690c6b 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml
@@ -5,7 +5,7 @@
             <thead>
                 <tr>
                     <td>Name</td>
-                    <td>Default with <literal>pmd</literal> plugin</td>
+                    <td>Default with <literal>findbugs</literal> plugin</td>
                 </tr>
             </thead>
             <tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.AbstractCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.AbstractCompile.xml
index 2b7d97d..d9ce73a 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.AbstractCompile.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.AbstractCompile.xml
@@ -14,7 +14,7 @@
             </tr>
             <tr>
                 <td>destinationDir</td>
-                <td><literal><replaceable>sourceSet</replaceable>.classesDir</literal></td>
+                <td><literal><replaceable>sourceSet</replaceable>.output.classesDir</literal></td>
             </tr>
             <tr>
                 <td>sourceCompatibility</td>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompile.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompile.xml
index 8fecbb5..98c4272 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompile.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.compile.GroovyCompile.xml
@@ -22,7 +22,7 @@
             </tr>
             <tr>
                 <td>groovyClasspath</td>
-                <td><literal>project.configuations.groovy</literal></td>
+                <td><literal>project.configurations.groovy</literal></td>
             </tr>
         </table>
     </section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLoggingContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLoggingContainer.xml
new file mode 100644
index 0000000..cfa0c9b
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.testing.logging.TestLoggingContainer.xml
@@ -0,0 +1,87 @@
+<!--
+  ~ Copyright 2012 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.
+  -->
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+<!--
+            <tr>
+                <td>events</td>
+            </tr>
+            <tr>
+                <td>minGranularity</td>
+            </tr>
+            <tr>
+                <td>maxGranularity</td>
+            </tr>
+            <tr>
+                <td>displayGranularity</td>
+            </tr>
+            <tr>
+                <td>showExceptions</td>
+            </tr>
+            <tr>
+                <td>showCauses</td>
+            </tr>
+            <tr>
+                <td>showStackTraces</td>
+            </tr>
+            <tr>
+                <td>showStandardStreams</td>
+            </tr>
+            <tr>
+                <td>exceptionFormat</td>
+            </tr>
+            <tr>
+                <td>stackTraceFilters</td>
+            </tr>
+-->
+            <tr>
+                <td>debug</td>
+            </tr>
+            <tr>
+                <td>info</td>
+            </tr>
+            <tr>
+                <td>lifecycle</td>
+            </tr>
+            <tr>
+                <td>warn</td>
+            </tr>
+            <tr>
+                <td>quiet</td>
+            </tr>
+            <tr>
+                <td>error</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
index 5e84298..f3d1990 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
@@ -16,8 +16,8 @@
             </tr>
             <tr>
                 <td>jdkName</td>
-                <td><literal>'1.6'</literal></td>
-                <td><literal>project.sourceCompatibility</literal></td>
+                <td>Java version used to run Gradle, for example <literal>'1.6'</literal></td>
+                <td>Java version used to run Gradle, for example <literal>'1.6'</literal></td>
             </tr>
             <tr>
                 <td>languageLevel</td>
diff --git a/subprojects/docs/src/docs/release/content/style.css b/subprojects/docs/src/docs/release/content/style.css
index 25bef77..c194d08 100644
--- a/subprojects/docs/src/docs/release/content/style.css
+++ b/subprojects/docs/src/docs/release/content/style.css
@@ -37,7 +37,7 @@ abbr, acronym {border-bottom:1px dotted #666;}
 address {margin:0 0 1.5em;font-style:italic;}
 del {color:#666;}
 pre {margin:1.5em 0;white-space:pre;}
-pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
+/*pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}*/
 li ul, li ol {margin:0;}
 ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;}
 ul {list-style-type:disc;}
@@ -85,7 +85,7 @@ body {
   color: #333;
 }
 
-h1,h2,h3,h4,h5,h6 {
+h1,h2,h3,h4 {
   color: #007042;
 }
 
@@ -107,15 +107,15 @@ section.major-detail {
   margin: 0 1em 2em;
   padding: 1em 1em 0;
   border-left: 2px solid #999;
-  font-size: 14px;
+  font-size: 15px;
   border: 1px dotted #999;
   background-color: #FCFCFC;
+  border-radius: 5px;
 }
 
 section.minor-detail {
   margin: 0 0.5em;
   padding: 0 1em;
-  font-size: 14px;
 }
 
 ul.toc-sub {
@@ -132,4 +132,46 @@ section.footer {
   padding-top: 1em;
   border-top: 1px dashed #999;
   text-align: center;
+}
+
+pre {
+  border: 1px solid #B3B3B3;
+  border-radius: 5px;
+  padding: 0.5em;
+  line-height: 1.4;
+  margin-left: 1em;
+  margin-right: 1em;
+}
+
+pre.code {
+  background-color: white;
+}
+
+pre.tt {
+  background-color: #333;
+  color: white;
+}
+
+code, tt {
+  font-family: Monaco, "Courier New", Courier, monospace;
+  font-size: 80%;
+}
+
+.java_keyword {
+  color: #708;
+}
+.java_comment {
+  color: #A70;
+}
+.java_plain, .java_type {
+  color: black;
+}
+.java_separator {
+  color: #666;
+}
+.java_operator {
+  color: #A22;
+}
+.java_literal {
+  color: #281;
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md
index bb1b7ed..37408a5 100644
--- a/subprojects/docs/src/docs/release/notes.md
+++ b/subprojects/docs/src/docs/release/notes.md
@@ -1,308 +1,216 @@
-Gradle 1.0 is a major step forward in the evolution of Gradle, and build tools in general.
+For Gradle 1.1 our focus has been on usability improvements, bug fixes, and ground work to support the post 1.0 evolution of Gradle. 
 
-## New and noteworthy
-
-Here are the new features introduced in Gradle 1.0, including the changes in all of the milestones.
-
-### Powerful dependency management
-Dependency management is at the heart of every build. In Gradle 1.0 we've moved away from using Ivy for dependency resolution. The dependency resolution engine has been rebuilt from the ground up; it is now faster, more accurate and more flexible.
-
-With full DSL support for Maven and Ivy repository formats, offline builds, the ability to refresh dependencies, and better control over version conflict resolution, we feel that Gradle 1.0 raises the bar for dependency management in build automation. As we continue to innovate in this space, we plan to take the dependency management that works so well for Java and adapt it to other languages and technologies.
-
-#### Offline builds
-
-You're not always connected to a network; Gradle 1.0 helps you out when you're offline. When run with the [`--offline`](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:cache_command_line_options) command-line option, Gradle will use whatever cached dependencies are available, failing early if a required dependency is not available in the cache. For Maven snapshots, changing modules and dynamic versions the most recent cached result will be used; no attempt will be mad [...]
-
-#### Validate and refresh dependencies
-
-When run with the [`--refresh-dependencies`](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:cache_command_line_options) command-line option, Gradle will validate all dependencies against the repositories declared for you build, downloading anything that is not up-to-date. As well as ensuring that you have the latest version of all dependencies, this process will check that every required dependency is available in a defined repository; just having the dependency in y [...]
-
-The `--refresh-dependencies` option is particularly helpful when things go wrong with dependency resolution; like when you are setting up a new repository and don't get it exactly right the first time. In such a case, being able to refresh your dependencies without deleting them from your cache directory can be very useful.
-
-Dependency validation utilises the Gradle change detection algorithm for remote repositories, so artifacts that are unchanged from the cached version will not be re-downloaded.
-
-#### Better control over version conflict resolution
-
-When your dependencies are resolved, the transitive dependency set may bring in several different versions of the same module. By default, Gradle's [conflict resolution](http://gradle.org/docs/1.0/userguide/dependency_management.html#sub:version_conflicts) will use the newest version. This is useful in most cases, but sometimes it isn't what you want, so Gradle 1.0 gives you control over the process by:
-
-* Declaring that your build should fail in the case of [version conflicts](http://gradle.org/docs/1.0/userguide/dependency_management.html#sub:version_conflicts).
-* Forcing a particular module version into the dependency set. This can be done [per-dependency](http://gradle.org/docs/1.0/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html) or more commonly [per-configuration](http://gradle.org/docs/1.0/dsl/org.gradle.api.artifacts.ResolutionStrategy.html).
-* [Excluding](http://gradle.org/docs/1.0/userguide/dependency_management.html#exclude-dependencies) a particular module or version from the dependency set.
-
-The options for handling resolution conflicts will continue to improve, giving users of Gradle full customization of the conflict resolution behaviour.
-
-#### Improved repository definition DSL
-
-While it is still possible to supply an Ivy DependencyResolver to locate your dependencies, in Gradle 1.0 we've improved our [repository definition DSL](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:repositories) to make it easier to declare the Ivy and Maven repositories you use for dependency resolution. Using the repository DSL to specify your repository type, location, layout and credentials is now simple and consistent.
-
-#### Publish artifacts other than archives
-
-Gradle has always made it easy to publish the default artifacts generated by your build, but it was not always trivial to publish additional files that weren't generated as a standard archive. Gradle 1.0 introduces the ability to [publish file-based artifacts](http://gradle.org/docs/1.0/userguide/artifact_management.html#N143A2) that are not produced by an archive task, simplifying the process of publishing arbitrary files to your repository.
-
-#### Dependency resolution explained
-
-Sometimes dependency resolution is a black box to the end user. We've attempted to make the whole process more understandable by improving the [documentation](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:dependency_resolution), log messages and error reporting.
-
-We've improved logging of dependency resolution; running Gradle with `--info --refresh-dependencies` will track all remote repository requests, helping to shed light on what's going on behind the scenes.
-
-### Fast, accurate and reliable dependency cache
-
-In Gradle 1.0 we've replaced the ivy artifact cache with our own implementation, targetting performance, consistency and reliability. Our new cache has advanced features that help avoid subtle (and not so subtle) problems permitted by other cache implementations, where a build that runs correctly on one machine fails on another.
-
-#### Origin aware
-The Gradle dependency cache remembers the origin of every dependency it holds, so your build can only access cached dependencies that come from a repository defined for your build. It is not possible for the presence of a cached artifact to mask the fact that a dependency is not available. This helps to ensure that your build will run correctly in any environment.
-
-#### Concurrency-safe cache
-There is no longer any need to declare separate user home directories or cache locations for different projects; the Gradle 1.0 dependency cache is fully thread-safe and multiprocess-safe.
+We focussed on improving Maven integration, dependency management and OSGi support, while also knocking off a few other known issues. For more information, check the [full list of resolved tickets](http://issues.gradle.org/sr/jira.issueviews:searchrequest-printable/temp/SearchRequest.html?jqlQuery=fixVersion+in+%28%221.1-rc-1%22%2C+%221.1-rc-2%22%29+ORDER+BY+priority&tempMax=1000). We did also manage to add some nice new features, outlined below.
 
-#### Improved change detection and reduced artifact downloads
+You might be interested in our [recent posting](http://forums.gradle.org/gradle/topics/the_gradle_release_approach) of what you can expect from us regarding release frequency, backwards compatibility and our deprecation policy. We have also written a [quick outlook on the upcoming 1.2 release](http://forums.gradle.org/gradle/topics/gradle_1_2_release_outlook?rfm=1).
 
-To reduce the number of downloads required, Gradle 1.0 has an improved algorithm for detecting if a cached artifact is up-to-date. By using published .sha1 files, ETags, and last-modified-date + content-length, Gradle will do everything it can to check if the version on the server is the same as the version you have already downloaded.
-
-#### Reuse previously downloaded artifacts from m2 repository and older Gradle caches
-
-If you're a maven user, in many cases the dependency you require is already available in your local m2 repository. To save downloading the dependency again, Gradle will reuse any locally available artifacts if they match the checksums published by a remote repository.
-
-#### Consistent caching for dynamic dependencies and changing modules.
-
-To improve performance, by default Gradle 1.0 will cache the resolved value of dynamic versions and changing modules for 24 hours. You can easily manage this timeout using the new [cache-control DSL](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:controlling_caching).
-
-The cache-control DSL is a work in progress; in the future Gradle will allow full programmatic control of all aspects of caching.
-
-#### More information
-
-You can find out more about the Gradle dependency cache in the [User Guide chapter](http://gradle.org/docs/1.0/userguide/dependency_management.html#sec:dependency_cache) and this series of forum posts: [part 1](http://forums.gradle.org/gradle/topics/welcome_to_our_new_dependency_cache), [part 2](http://forums.gradle.org/gradle/topics/dependency_resolution_improvements), and [part 3](http://forums.gradle.org/gradle/topics/dependency_resolution_in_gradle_1_0_milestone_8).
-
-### Faster builds
-
-A fast build means faster feedback. Gradle 1.0 includes a host of performance improvements.
-
-* Dependency resolution is faster, in some cases many times faster.
-* Gradle daemon enables developers to run builds faster.
-* Java and Groovy compilation run in less time.
-* Up-to-date checks are faster for large source sets.
-
-We've also enhanced the Gradle profile report, which now includes dependency resolution times in an improved layout.
-
-#### Faster dependency resolution
-
-We've done a lot of work to make dependency resolution as fast as possible, while staying reproducible and accurate. 
-
-To do this we:
-
-* Greatly reduced the number of HTTP requests required to download a dependency.
-* Cache the absence of artifacts per repository, so we don't need to check on every resolve.
-* Implemented a new resolve engine which is much faster and more maintainable than the ivy-based one used previously.
-* Improved cache locking and lock escalation so that it is as efficient as possible and yet concurrency safe.
-* Reuse artifacts previously downloaded by Maven or an older version of Gradle.
-
-Faster dependency resolution also means faster up-to-date checks for speedy incremental builds.
-
-#### Improved Gradle daemon
-We've done a lot of work stabilising the [Gradle daemon](http://gradle.org/docs/1.0/userguide/gradle_daemon.html), and it is no longer marked as experimental. The Gradle daemon reduces the startup time of every `gradle` invocation, and we recommend it for all developer builds.
-
-#### Java and Groovy compilation is faster
-By default, Gradle 1.0 no longer uses Ant tasks for Java and Groovy compilation. By removing the task overhead, in-process compilation has been made faster.
-
-When configured to compile in a separate process (`compile.options.fork = true`), Gradle will now use a single daemon compiler process to further speed up the compilation of your source files. This means that the compile process only needs to be forked once per build invocation, dramatically reducing compile times for large multi-project builds.
-
-#### Up-to-date checks are faster for large source sets.
-By reading cached history more efficiently for large set of input and outputs, Gradle 1.0 performs up-to-date checks faster for large sources sets. With faster up-to-date checks your incremental builds will be faster, reducing the time to re-build when little has changed.
-
-#### Build profile report
-When run with the [`--profile`](http://gradle.org/docs/1.0/userguide/tutorial_gradle_command_line.html#sec:profiling_build) command-line option, Gradle will generate a report detailing the breakdown of time spent executing your build, including dependency resolution time. This report can provide hints about the critical things that slow down your build, helping you make your build faster.
-
-### Java code quality plugins
-Gradle has always provided great out-of-the-box support for standard Java applications. With Gradle 1.0 we've included a number of new code quality integrations to make it easy for you to include these important tools in your build process.
-
-Supported tools include:
+## New and noteworthy
 
-* Sonar
-* FindBugs
-* PMD
-* Checkstyle
-* CodeNarc
+### Test Logging
 
-#### Sonar plugin
-Using the [Gradle Sonar plugin](http://gradle.org/docs/1.0/userguide/sonar_plugin.html), you can now easily integrate your build results with Sonar, the web based platform for monitoring code quality.
+Gradle 1.1 provides much more detailed information during test execution, right on the console. We've worked hard to make the the new output useful and informative out of the box, but we've also given you the ability to finely tune it to your liking.
 
-#### FindBugs plugin 
-FindBugs uses static analysis to look for bugs in Java code. The [Gradle FindBugs plugin](http://gradle.org/docs/1.0/userguide/findbugs_plugin.html) adds a task for every source set to scan the Java bytecode for a list of bug patterns.
+The old output:
 
-#### JDepend plugin 
-JDepend allows you to generate design quality metrics, by analyzing the coupling between Java class files. The [Gradle JDepend plugin](http://gradle.org/docs/1.0/userguide/jdepend_plugin.html) adds Gradle tasks that analyze all inter-package dependencies of a source set, producing a report that can help you to find unexpected couplings, package dependency cycles, and refactoring targets.
+<pre><tt>:spring-integration-twitter:test
+Test o.s.i.t.i.MessageSourceTests FAILED
+4 tests completed, 1 failure
+</tt>
+</pre>
 
-#### PMD plugin
-The popular PMD project provides tools to inspect Java source code looking for potential bugs, inefficiencies and duplication. The [Gradle PMD plugin](http://gradle.org/docs/1.0/userguide/pmd_plugin.html) adds a task that will produce a PMD report per defined source set. The plugin provides many configuration options, including the ability to define custom rulesets, and which version of PMD to use.
+The improved output:
 
-#### Separate Checkstyle and CodeNarc plugins
-You can now apply the [Gradle Checkstyle plugin](http://gradle.org/docs/1.0/userguide/checkstyle_plugin.html) and the [Gradle CodeNarc plugin](http://gradle.org/docs/1.0/userguide/codenarc_plugin.html) separately to your build. Both of these plugins have been improved to provide more flexibility, and you can now specify exactly which version of each tool to use.
+<pre><tt>:spring-integration-twitter:test
+o.s.i.t.i.MessageSourceTests > testSearchReceivingMessageSourceInit  FAILED
+    j.f.AssertionFailedError at MessageSourceTests.java:96
 
-### IDE integration
+4 tests completed, 1 failed, 1 skipped
+</tt>
+</pre>
 
-Whether you develop code in Eclipse STS, Intellij IDEA or NetBeans (experimental), there is a native Gradle integration for your IDE. Using native IDE integrations you can import and run Gradle builds directly into your IDE, and keep your IDE settings in sync with your Gradle build definition.
-While not strictly part of the Gradle distribution, these native integrations continually improve as Gradle provides more features via the Tooling API - the new embeddable API on which these integrations are based. 
+#### Show Exceptions
 
-If you don't require tight native integration, the Gradle IDE plugins help you by generating standard project files for your environment. We've improved these IDE plugins to provide more configuration options, to have better defaults, to run faster and to smoothly configure your IDE for your Gradle project.
+One of the most useful options is to show the exceptions thrown by failed tests. By default, Gradle will log a succinct message for every test exception. To get more detailed output, configure the `exceptionFormat`:
 
-#### Gradle IDE plugins - generating project files for your IDE
+    test {
+        testLogging {
+            exceptionFormat "full"
+        }
+    }
 
-The new Gradle IDE plugins generate IDE project files based on the project defined in your Gradle build file.  The [Gradle Eclipse plugin](http://gradle.org/docs/1.0/userguide/eclipse_plugin.html) makes it easy to keep your .classpath and .project files in sync with your Gradle build, while the [Gradle IDEA plugin](http://gradle.org/docs/1.0/userguide/idea_plugin.html) does the same for your .ipr and .iml files.
+Which would produce output like:
 
-As usual with Gradle, you have full programmatic control of the generated model. The DSL reference for the [idea](http://gradle.org/docs/1.0/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.html) and [eclipse](http://gradle.org/docs/1.0/dsl/org.gradle.plugins.ide.eclipse.model.EclipseProject.html) plugins contains many code samples, demonstrating how to fine-tune the configuration and ensure your project imports easily into the IDE. 
+<pre><tt>o.s.i.t.i.MessageSourceTests > testSearchReceivingMessageSourceInit FAILED
+    j.f.AssertionFailedError: null
+        at j.f.Assert.fail(Assert.java:47)
+        at j.f.Assert.assertTrue(Assert.java:20)
+        at j.f.Assert.assertTrue(Assert.java:27)
+        at o.s.i.t.i.MessageSourceTests.testSearchReceivingMessageSourceInit(MessageSourceTests.java:96)
 
-#### Native Eclipse STS support
-You can now import Gradle projects and run Gradle builds directly inside of Eclipse, using the [Eclipse STS Gradle plugin](http://static.springsource.org/sts/docs/latest/reference/html/gradle). We are very grateful to our friends at SpringSource for making this possible, and we continue to collaborate to make this integration even better.
+4 tests completed, 1 failed, 1 skipped
+</tt>
+</pre>
 
-#### Native Intellij IDEA support
-Using the [Intellij IDEA Gradle plugin](http://www.jetbrains.com/idea/webhelp/gradle-2.html) you can easily import an existing Gradle project directly into Intellij IDEA, with many more features planned in the near future. Many thanks to JetBrains for their continued development of this plugin.
+#### Stack Trace Filters
 
-#### Native NetBeans support (experimental)
-The [NetBeans Gradle plugin](http://plugins.netbeans.org/plugin/41776/gradle) provides the ability to import and run Gradle build files directly in NetBeans. Thanks to the NetBeans team for developing this plugin. 
+Stack traces of test exceptions are automatically truncated not to show anything below the entry point into the test code. This filters out Gradle internals and internals of the test framework. A number of other filters are available. For example, when dealing with Groovy code it makes sense to add the `groovy` filter:
 
-This NetBeans integration is still experimental, but we hope that by leveraging the power of the Gradle Tooling API the plugin will continue to evolve and improve in the future.
+    test {
+        testLogging {
+            stackTraceFilters "truncate", "groovy"
+        }
+    }
 
-### Embedding Gradle via the Tooling API
+While would produce output like:
 
-Gradle 1.0 sees the introduction of the Gradle tooling API, a new way to embed Gradle. This API allows you to execute and monitor builds, and to query for details about the build. The Tooling API takes care of downloading the version of Gradle required to execute a particular build, and utilises the Gradle daemon process for lightning fast command execution.
+<pre><tt>o.s.i.t.i.MessageSourceTests > testSearchReceivingMessageSourceInit FAILED
+    o.s.i.MessageDeliveryException: Failed to send tweet 'Liking the new Gradle test logging output!'
+        at o.s.i.t.i.SearchReceivingMessageSource.<init>(SearchReceivingMessageSource.java:42)
+        at o.s.i.t.i.MessageSourceTests.testSearchReceivingMessageSourceInit(MessageSourceTests.java:81)
+        Caused by:
+        o.s.i.MessageSourceException: Oops! Looks like Twitter is down. Try again shortly.
+            at o.s.i.c.IntegrationObjectSupport.onInit(IntegrationObjectSupport.java:113)
+            at o.s.i.t.i.AbstractTwitterMessageSource.onInit(AbstractTwitterMessageSource.java:92)
+            at o.s.i.t.i.SearchReceivingMessageSource.<init>(SearchReceivingMessageSource.java:40)
+            ... 1 more
+</tt>
+</pre>
 
-The Gradle Tooling API operates in a version independent manner, meaning that any given version of the Tooling API is able to execute builds using both newer and older Gradle versions. Your Gradle version isn't tied to the version of the Tooling API being used, allowing tools like the native IDE plugins to remain stable while supporting a wide range of Gradle projects.
+#### Show Other Test Events
 
-#### Easy to embed in your application
-The [Gradle Tooling API](http://gradle.org/docs/1.0/userguide/embedding.html) implementation is lightweight, with only a small number of dependencies: you don't need the Gradle distribution to use the Tooling API. As a well-behaved library, it makes no assumptions about your class loader structure or logging configuration. These reasons ensure that the Gradle Tooling API is easy to bundle inside your application.
+Besides a test having failed, a number of other test events can be logged:
 
-#### Compatible with multiple Gradle versions
-Using the Gradle Tooling API allows you to execute builds using many different versions of Gradle: the version of the Tooling API is not tied to the version of Gradle executing. This cross-version compatibility is constantly verified by our extensive test suite, which validates the correct function of Tooling API features across all Gradle versions. What's important is that we don't just test backwards compatibility but also forward compatibility, so you can be sure that you'll be able t [...]
+    test {
+        testLogging {
+            events "started", "passed", "skipped", "failed", "standardOut", "standardError"
+            minGranularity 0
+        }
+    }
 
-### Gradle Build Daemon
+By setting `minGranularity`, these events aren't only shown for individual tests, but also for test classes and suites.
 
-The Gradle build daemon reduces the startup and execution time of builds by running them in a long-lived daemon process. The <code>gradle</code> command becomes a client process that communicates with the daemon process, avoiding various JVM startup costs and giving the JVM an opportunity to optimize hotspots.
+#### Individual Logging Per Log Level
 
-Gradle 1.0 brings a host of improvements to the daemon, and we now recommend it for all developer builds.
+Test logging can be configured separately per log level:
 
-#### Ready for prime-time
+    test {
+        testLogging {
+            quiet {
+                events "failed"
+            }
+        }
+    }
 
-In earlier versions of Gradle, the daemon was considered an experimental feature. Much work has gone into stabilizing the daemon since it was introduced and we are now recommending it for all developer builds. There are still many improvements planned in the upcoming Gradle releases, but we believe that most of the stability issues have been resolved.
+On log levels `LIFECYCLE`, `INFO`, and `DEBUG`, some test events (most importantly failed tests) are already shown by default. For detailed documentation about all test logging related options, see [TestLogging](javadoc/org/gradle/api/tasks/testing/logging/TestLogging.html) and [TestLoggingContainer](javadoc/org/gradle/api/tasks/testing/logging/TestLoggingContainer.html).
 
-While you can explicitly enable the daemon for a particular build execution with the `--daemon` command-line option, the best way to make the daemon your default is by configuring the [org.gradle.daemon](http://gradle.org/docs/1.0/userguide/build_environment.html#sec:gradle_configuration_properties) property.
-            
-#### Compatible with builds requiring user interaction
+For further details, see the [forum announcement](http://forums.gradle.org/gradle/topics/whats_new_in_gradle_1_1_test_logging) for this feature.
 
-Some builds consume the standard input, for example, to perform the interaction with the user. The daemon fully supports those kinds of builds, by forwarding any client input to the executing build process.
+### Easier opening of test and code quality reports
 
-#### Respects client java version
+When a test or code quality task has found a problem, it typically prints a message which includes the file path of the generated report. This message has been
+slightly changed to print the path as a file URL. Smart consoles recognize such URLs and make it easy to open them. For example, in Mac's Terminal.app reports are now
+just a CMD + double click away.
 
-The java version the daemon uses is configurable. In case you run different builds with different java versions multiple daemons will be spawned to avoid compatibility issues.
+### Tooling API provides Gradle module information for external dependencies
 
-#### Future plans
-Longer term, the daemon will offer even more features that improve the performance of your build. Performing up-to-date checks, dependency resolution and project evaluation pre-emptively are just some of many planned features. The Tooling API - an official way to embed Gradle - fully takes advantage of the build daemon.
+The Tooling API can be used to obtain the model of the project which includes the information about the dependencies of the project. In Gradle 1.1, the Tooling API also provides Gradle
+module information, i.e. group, name, version of each dependency.
 
-### Enterprise scale
+Please see the javadoc for [GradleModuleVersion](javadoc/org/gradle/tooling/model/GradleModuleVersion.html). You can obtain the Gradle module information via [ExternalDependency.getGradleModuleVersion()](javadoc/org/gradle/tooling/model/ExternalDependency.html#getGradleModuleVersion\(\)).
 
-Working at the enterprise scale means more than just a fast build. Gradle 1.0 adds capabilities to help you push build logic and configuration out to developers and across teams in a controlled way.
+This feature helps the IDE integration like Gradle STS plugin to support
+more flexible developer workspace and hence faster dev cycles.
+Using Eclipse terms: it is a first step into providing the ability to toggle between
+Eclipse 'project' dependency and a regular 'binary' dependency.      
 
-Gradle enables zero-configuration on the client side. Any necessary configuration, be it JVM args or repository definitions, can be specified per project, team or enterprise and stored in version control. Bundle these definitions into a corporate plugin and distribute with the Gradle wrapper for a powerful way to manage your enterprise build environment. No more complex wiki pages explaining how to configure the build on a new machine. No more wasted time due to false alarms caused by a  [...]
+### Global Maven settings.xml
 
-Making it easy to administer the enterprise build environment was a core goal of Gradle 1.0. We will continue to innovate in this area in future releases.
+When migrating from Maven you can reuse the artifacts from your local Maven repository in your Gradle build. Gradle now honours the Maven settings located in <code><em>$M2_HOME</em>/conf/settings.xml</code> to locate the local Maven repository. If a local repository is defined in `~/.m2/settings.xml`, this location takes precedence over a repository definition in <code><em>$M2_HOME</em>/conf/settings.xml</code>. This is used when you have declared the `mavenLocal()` repository definition [...]
 
-#### The Gradle wrapper
+### Publishing SHA1 checksums to Ivy repositories
 
-The [Gradle wrapper](http://gradle.org/docs/1.0/userguide/gradle_wrapper.html) is invaluable in a large team to guarantee that a particular Gradle version is used to execute your build. This provides a consistent build environment, enabling reproducible and maintainable automation.
+Gradle will now automatically generate and publish SHA1 checksum files when publishing to an Ivy repository. For each file `foo.ext` published, Gradle will also publish a checksum file with the name `foo.ext.sha1`. 
 
-The Gradle wrapper automatically downloads and installs the required Gradle distribution, just checkout the project from version control and run. No previous install of Gradle is required! Upgrading to a new version of Gradle is trivial with the wrapper. Increment the version number in your wrapper configuration script, and anyone running your build will automatically switch to use the new Gradle version.
+The presence of SHA1 checksums in the dependency repository allows Gradle to be more efficient when resolving dependencies. Recently Gradle gained the ability to use the checksum of a remote file to determine whether or not it truly needs to be downloaded. If Gradle can find a file with an identical checksum to the target remote file, it will use that instead of downloading the remote file. Also, for “changing” dependencies (e.g. snapshots) Gradle can compare the remote checksum with the [...]
 
-#### Define JVM arguments and Java version required to build your project
+Checksums have always been published to Maven repositories. This new feature of publishing checksums to Ivy repositories unlocks the recent Gradle dependency downloading optimizations to Ivy users. There is no change required to your build script to enable this functionality.
 
-In many cases your build will only execute on a particular java version, or with certain memory settings. Rather than having developers tweak their local environment to run your build, Gradle allows you to [explicitly configure the JVM args and Java location for your build](http://gradle.org/docs/1.0/userguide/build_environment.html). These settings can then be checked-in to version control and become part of a versioned build environment, improving the maintainability and consistency of [...]
+### Dependency resolution supports HTTP Digest Authentication
 
-The build environment configuration is honoured by both daemon and non-daemon build executions. However, when configuring build environment settings we strongly recommend the use of the Gradle daemon on development machines, to avoid the performance penalty of starting an additional JVM on each build execution.
+Due to the way Gradle used pre-emptive HTTP Authentication, Gradle 1.0 was not able to handle a repository secured with HTTP Digest Authentication. This issue has been resolved by using the following new strategy:
 
-#### Use an init script to define common build components
-Using a [Gradle init script](http://gradle.org/docs/1.0/userguide/init_scripts.html), it is possible to configure standard plugins, define standard repositories or apply certain common conventions across a wide range of projects. This feature makes it easy to use a particular standard plugin in a project without the hassle of configuring the build script classpath.
+* Any GET/HEAD request issued by Gradle will no longer contain pre-emptive HTTP Authentication headers.
+* An initial PUT/POST request will contain Basic Authentication headers for pre-emptive HTTP Authentication.
+    * If the server requires HTTP Basic Authentication, then this request will succeed automatically
+    * If the server requires HTTP Digest Authentication, then this request will fail with a 401, and we will re-send the request with the correct headers.
+* After the initial PUT/POST request, subsequent requests to the repository will have correct Auth headers, and will not require re-send.
 
-Init scripts are very flexible, and may be applied to a single build execution, to all builds run by a particular user, or to all builds executed by a particular Gradle installation. In Gradle 1.0 it is now possible to have [multiple init scripts](http://gradle.org/docs/1.0/userguide/init_scripts.html#N15350) specified, which will be applied to your build environment in a well-defined order.
+## Upgrading from Gradle 1.0
 
-#### Assemble a custom Gradle distribution for the enterprise
-Many large organisations desire a consistent set of environments, rules or plugins to be applied to all builds in the enterprise. By constructing Gradle init scripts that specify these corporate standards and bundling these with a regular Gradle distribution, you can construct a build tool that specifically enables and enforces your corporate standards. When combined with the powerful distribution mechanism of the Gradle wrapper, a tailored Gradle install can enable you to provide build  [...]
+Please let us know if you encounter any issues during the upgrade to Gradle 1.1, that are not listed below.
 
-### C++ support
+### Deprecations
 
-Gradle 1.0 includes preliminary [support for building C++ based projects](http://gradle.org/docs/1.0/userguide/cpp.html) on both Windows and UNIX like platforms. The `cpp-exe` and `cpp-lib` plugins can be used for building native executables and libraries respectively from C++ source code.
+#### Statement Labels
 
-#### Future plans
-These plugins are in the early stages of development, but can already be used to generate native binaries.
+As in Java, statement labels are rarely used in Groovy. The following example shows a frequent pitfall where a statement label is erroneously used in an attempt to configure an object:
 
-Currently, there is no direct support for creating multiple variants of the same binary (e.g. 32 bit vs. 64 bit) and there is no direct support for cross platform source configuration (á la autoconf) at this time. Support for different compiler chains, managing multiple variants and cross platform source configuration will be added over time, making Gradle a fully capable build tool for C++ (and other "native" language) projects.
+    task foo {
+        dependsOn: bar
+    }
 
-The Gradle development team would like to encourage all users interested in C++ support to experiment with the existing functionality and provide feedback via the [Gradle Forums](http://forums.gradle.org/).
+Whereas what the author actually intended was:
 
-### More plugins and simpler build scripts
+    task foo {
+        dependsOn bar
+    }
 
-Gradle is committed to making it easier for you to develop software. We continue to add new plugins, conventions and features that make the everyday use of Gradle more convenient. More plugins means more build-by-convention support, reduced configuration and less custom build logic.
+Note the colon after `dependsOn` in the first code block. This extra colon causes the line to be interpreted as a statement label (a Java/Groovy language feature), which effectively makes it a non operation. Statement labels are not useful in Gradle build scripts.
 
-Gradle now supports several different types of projects: plugins include support for building:
+To prevent such mistakes that are hard to track down and debug, the usage of statement labels in build scripts has been deprecated and Gradle will issue a deprecation warning when they are used.
+In Gradle 2.0, statement labels in build scripts will no longer be supported and will cause an error.
 
-* C++ libraries and executables
-* Applications that run on the JVM
-* Java EE Web applications
-* Java EE Enterprise applications
+#### M2_HOME system property
 
-Gradle 1.0 also includes numerous improvements to the build DSL and new features to make your build simpler and easier to maintain. 
+Previously, Gradle looked for a JVM system property named `M2_HOME` for the location of a custom Maven home directory. This has been deprecated in favor of an environment variable, named `M2_HOME`, which is also used by other tools that integrate with Maven.
+Support for the `M2_HOME` system property will be removed in Gradle 2.0.
 
-#### Application plugin
+#### Publication of missing artifacts
 
-The [Application plugin](http://gradle.org/docs/1.0/userguide/application_plugin.html) simplifies the creation of executable JVM based applications. You can use it to run your application directly from your Gradle build, and it will bundle your application for distribution. The packaged application includes automatically generated start scripts for windows and unix systems, third party dependencies and custom distribution files like licenses or documentation.
+Previously, if an artifact to be published referred to a file that does not exist during publication, then Gradle would silently ignore the artifact to be published. This is only likely to occur when declaring [file artifacts](userguide/artifact_management.html#N143A6).
 
-#### Ear plugin
-The [Ear Plugin](http://gradle.org/docs/1.0/userguide/ear_plugin.html) adds support for creating Java EE Enterprise Archives (EAR files). It provides EAR specific dependency management and enables customization of the deployment descriptor.
+This behavior is now deprecated. Attempting to publish a non-existant artifact file will result in a deprecation warning, and will produce an error in Gradle 2.0.
 
-#### Signing plugin
+#### DSL
 
-Digital signatures can be used for tracking build artifacts and files. These signatures allow users to verify who built the artifact and when the signature was created. The [Signing Plugin](http://gradle.org/docs/1.0/userguide/signing_plugin.html) enables the user to:
+##### `Project.fileTree(Object)` - Removal of incorrect `@deprecation` tag
 
-* Define which artifacts to sign.
-* Configure required credentials.
-* Specify certain conditions that require signing, such as only signing for release versions.
-* Publish digital signatures and signed POM files.
+The `Project.fileTree(Object)` method was incorrectly annotated with the `@deprecated` Javadoc tag in Gradle 1.0-milestone-8. This method has not been deprecated and the Javadoc tag has been removed.
 
-#### Announce plugin
-The [Gradle Announce Plugin](http://gradle.org/docs/1.0/userguide/announce_plugin.html) enables the build to announce custom messages to the user at any or every phase of the build. This feature can work well together with `--continue` (see below), by reporting on any failures that occur in a continued build.
+##### `Project.fileTree(Closure)` - Addition of `@deprecation` tag
 
-The announce plugin integrates nicely with different desktop messaging systems like [Snarl](https://sites.google.com/site/snarlapp/home), [Growl](http://growl.info/) and [Ubuntu Notify](http://www.ubuntu.com/) and also with the internet messaging system [Twitter](http://twitter.com/).
+The `Project.fileTree(Closure)` method was deprecated in Gradle 1.0-milestone-8. The method was not annotated with the `@deprecated` javadoc tag at that time. This has been added for this release.
+This method will be removed in Gradle 2.0.
 
-#### Build continuation
-It can be annoying to come back from lunch, only to find that your build failed early on checkstyle and never got around to running your tests. The [`--continue`](http://gradle.org/docs/1.0/userguide/gradle_command_line.html) command line option provides experimental support for continuing a build after a task fails: the end result of the build is the original failure, but the remainder of the build will still execute.
+#### API
 
-Note that this feature is a work in progress, and is not yet feature complete. In particular, only the first failure is captured and displayed at the end of the build process: you will need to inspect the build output to see what other failures may have occurred in a continued build.
+##### `org.gradle.api.tasks.testing.TestLogging` - Moved into `logging` subpackage
 
-#### Log test output to the console
+The `org.gradle.api.tasks.testing.TestLogging` interface was moved into package `org.gradle.api.tasks.testing.logging` (and subsequently enhanced with new methods). For backwards compatibility reasons, the old interface was kept at its original location,
+but is now deprecated. The old interface will be removed in Gradle 2.0.
 
-Gradle strives to provide a clean and concise console output, without unnecessary clutter that hides the important things. So by default Gradle does not show the output of the tests on the console. Gradle 1.0 provides a a simple way to [display test output on the console](http://gradle.org/releases/1.0-milestone-6/docs/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testLogging). You can also hook in a listener to have [full control what test output is shown] [...]
+### Potential breaking changes
 
-#### Better diagnosis tools
-In Gradle 1.0 we're providing better error messages, more documentation and improved samples to help you streamline any troubleshooting. The new [extra properties](http://gradle.org/docs/1.0/userguide/writing_build_scripts.html#sec:extra_properties) mechanism retains the ability to add ad-hoc properties to your build script, which making it much easier to catch typos.
+##### `idea.project.jdkName`
 
-Naturally there is more work to do, and if you have questions don't hesitate to post them on the [Gradle forums](http://forums.gradle.org).
+We've decided to change the IDEA plugin's default JDK name. The new default is now smarter. Without this change, many users had to configure the JDK name explicitly in the builds or manually tweak the JDK name in IDEA after running the `gradle idea` task. The current default uses the Java version that Gradle runs with.
 
-## Upgrading from an earlier Gradle version
-For details of exactly what was changed in which Gradle 1.0 milestone, please see the release notes of each individual release. You can also consult the migration guide for each milestone release to identify issues that may affect you during an upgrade to Gradle 1.0.
+Although we believe the new default is much better for majority of users, there might be some builds out there that preferred the old default. If you happen to prefer the old default (`1.6`) please configure that explicitly in your build via [idea.project.jdkName](dsl/org.gradle.plugins.ide.idea.model.IdeaProject.html#org.gradle.plugins.ide.idea.model.IdeaProject:jdkName)
 
-### Previous Release Notes
+##### `AbstractTask.getDynamicObjectHelper()`
 
-* [Gradle-1.0-rc-3](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-rc-3+Release+Notes)
-* [Gradle-1.0-rc-2](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-rc-2+Release+Notes)
-* [Gradle-1.0-rc-1](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-rc-1+Release+Notes)
-* [Gradle-1.0-milestone-9](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-9+Release+Notes)
-* [Gradle-1.0-milestone-8a](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-8a+Release+Notes)
-* [Gradle-1.0-milestone-8](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-8+Release+Notes)
-* [Gradle-1.0-milestone-7](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-7+Release+Notes)
-* [Gradle-1.0-milestone-6](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-6+Release+Notes)
-* [Gradle-1.0-milestone-5](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-5+Release+Notes)
-* [Gradle-1.0-milestone-4](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-4+Release+Notes)
-* [Gradle-1.0-milestone-3](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-3+Release+Notes)
-* [Gradle-1.0-milestone-2](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-2+Release+Notes)
-* [Gradle-1.0-milestone-1](http://wiki.gradle.org/display/GRADLE/Gradle+1.0-milestone-1+Release+Notes)
+Deprecated internal method `AbstractTask.getDynamicObjectHelper()` has been removed.
 
 ## Fixed Issues
 
-The list of issues fixed between 1.0-milestone-9 and 1.0 can be found [here](http://issues.gradle.org/sr/jira.issueviews:searchrequest-printable/temp/SearchRequest.html?jqlQuery=fixVersion+in+%28%221.0-rc-1%22%2C+%221.0-rc-2%22%2C+%221.0-rc-3%22%2C+%221.0%22%29+ORDER+BY+priority&tempMax=1000).
\ No newline at end of file
+The list of issues fixed between 1.0 and 1.1 can be found [here](http://issues.gradle.org/sr/jira.issueviews:searchrequest-printable/temp/SearchRequest.html?jqlQuery=fixVersion+in+%28%221.1-rc-1%22%2C+%221.1-rc-2%22%29+ORDER+BY+priority&tempMax=1000).
diff --git a/subprojects/docs/src/docs/userguide/announcePlugin.xml b/subprojects/docs/src/docs/userguide/announcePlugin.xml
index 533bde1..bbd7b7d 100644
--- a/subprojects/docs/src/docs/userguide/announcePlugin.xml
+++ b/subprojects/docs/src/docs/userguide/announcePlugin.xml
@@ -1,112 +1,81 @@
 <chapter id='announce_plugin'>
     <title>The Announce Plugin</title>
-    <para>The Gradle announce plugin enables you to publish messages on succeeded tasks to your favourite platforms.
-	It supports 
+    <para>The Gradle announce allows to send custom announcements during a build. The following notification systems are supported:
 	<itemizedlist>
  		<listitem><ulink url='http://twitter.com'>Twitter</ulink></listitem>
- 		<listitem><ulink url='http://ubuntu.com'>Ubuntu Notify</ulink></listitem>
-        <listitem><ulink url="https://sites.google.com/site/snarlapp/home">Snarl</ulink>, a Windows Notification System</listitem>
-        <listitem><ulink url="http://growl.info/">Growl</ulink>, a Mac OS X Notification System</listitem>
+ 		<listitem><ulink url='http://manpages.ubuntu.com/manpages/gutsy/man1/notify-send.1.html'>notify-send</ulink> (Ubuntu)</listitem>
+        <listitem><ulink url="https://sites.google.com/site/snarlapp/home">Snarl</ulink> (Windows)</listitem>
+        <listitem><ulink url="http://growl.info/">Growl</ulink> (Mac OS X)</listitem>
  	</itemizedlist>
-	
 	</para>
 
     <section>
         <title>Usage</title>
-        <para>To use the announce plugin, include in your build script:</para>
+        <para>To use the announce plugin, apply it to your build script:</para>
         <sample id="useAnnouncePlugin" dir="announce" title="Using the announce plugin">
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
 
-	<para>After that, configure you username and password (if required for the service you want to announce to) with:</para>
-	<sample id="useAnnouncePlugin" dir="announce" title="Configure the announce plugin">
+        <para>Next, configure your notification service(s) of choice (see table below for which configuration properties are available):</para>
+        <sample id="useAnnouncePlugin" dir="announce" title="Configure the announce plugin">
             <sourcefile file="build.gradle" snippet="announce-plugin-conf"/>
         </sample>
 
-	<para>Finally, you can use announce with any task by attaching it via task.dolast() as shown below</para>
-	<sample id="useAnnouncePlugin" dir="announce" title="Using the announce plugin">
+        <para>Finally, send announcements with the <literal>announce</literal> method:</para>
+        <sample id="useAnnouncePlugin" dir="announce" title="Using the announce plugin">
             <sourcefile file="build.gradle" snippet="announce-usage"/>
         </sample>
 
-	<para>As you can see, the syntax in <literal>.doLast</literal> is
-
-		 <cmdsynopsis>
-			<command> announce("MESSAGE", "TARGET")</command>
-		 </cmdsynopsis>
-
-		Where MESSAGE is any GString you pass (and might have constructed before). And TARGET might one of the following:
-
-	</para>
+        <para>The <literal>announce</literal> method takes two String arguments: The message to be sent, and the notification
+            service to be used. The following table lists supported notification services and their configuration properties.
+        </para>
     </section>
+
    <table>
-       <title>announce plugin targets</title>
+       <title>Announce Plugin Notification Services</title>
        <thead>
            <tr>
-               <td>target literal</td>
-               <td>target</td>
-               <td>configuration parameters</td>
-               <td>more information</td>
+               <td>Notification Service</td>
+               <td>Operating System</td>
+               <td>Configuration Properties</td>
+               <td>Further Information</td>
            </tr>
        </thead>
        <tr>
            <td>twitter</td>
-           <td>Twitter</td>
-           <td>username , password</td>
+           <td>Any</td>
+           <td>username, password</td>
            <td></td>
        </tr>
        <tr>
            <td>snarl</td>
-           <td>Snarl Windows Notification Service</td>
+           <td>Windows</td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td>growl</td>
-           <td>Growl Mac OS X Notification Service</td>
+           <td>Mac OS X</td>
            <td></td>
            <td></td>
        </tr>
        <tr>
            <td>notify-send</td>
-           <td>Notify Ubuntu Notification Service</td>
+           <td>Ubuntu</td>
+           <td></td>
+           <td>Requires the notify-send package to be installed. Use <literal>sudo apt-get install libnotify-bin</literal>
+               to install it.</td>
+       </tr>
+       <tr>
+           <td>local</td>
+           <td>Windows, Mac OS X, Ubuntu</td>
            <td></td>
-           <td>You need to have notify-send installed for this. Run <literal>sudo apt-get install libnotify-bin</literal>
-               on Ubuntu to install it.</td>
+           <td>Automatically chooses between snarl, growl, and notify-send depending on the current operating system.</td>
        </tr>
    </table>
 
     <section>
-        <title>Tasks</title>
-        <para>TBD</para>
-    </section>
-
-    <section>
-        <title>Project layout</title>
-        <para>TBD</para>
-    </section>
-
-    <section>
-        <title>Dependency management</title>
-        <para>TBD</para>
-    </section>
-
-    <section>
-        <title>Convention properties</title>
-
-        <para>The announce plugin adds an 
-	
-	TBD
-        </para>
-     
-        <para>
-        	
-
-        </para>
-      <!--  <sample id="anouncePlugin" dir="userguide/tutorial/announce" title="example of annunce plugin usage.">
-            <sourcefile file="build.gradle" snippet="full-example"/> 
-        </sample>-->
-        <para>
-	TBD
-	</para>
+        <title>Configuration</title>
+        <para>See <apilink class="org.gradle.api.plugins.announce.AnnouncePluginExtension"/>.</para>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml b/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
index e1b3352..f8a37c1 100644
--- a/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/buildScriptsTutorial.xml
@@ -165,8 +165,8 @@
             and set like a predefined task property.
         </para>
         <sample id="extraTaskProperties" dir="userguide/tutorial/extraProperties" title="Adding extra properties to a task">
-            <sourcefile file="build.gradle" snippet="taskProperty"/>
-            <output args="-q showProps"/>
+            <sourcefile file="build.gradle" snippet="taskProperties"/>
+            <output args="-q printTaskProperties"/>
         </sample>
         Extra properties aren't limited to tasks. You can read more about them in <xref linkend='sec:extra_properties'/>.
     </section>
diff --git a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
index 8b98cd6..9042d35 100644
--- a/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/commandLineTutorial.xml
@@ -78,13 +78,16 @@
     <section id="sec:selecting_build">
         <title>Selecting which build to execute</title>
         <para>When you run the <command>gradle</command> command, it looks for a build file in the current directory.
-            You can use the <option>-b</option> option to select another build file. For example:
+            You can use the <option>-b</option> option to select another build file.
+            If you use <option>-b</option> option then <filename>settings.gradle</filename> file is not used. Example:
         </para>
         <sample id="selectProjectUsingBuildFile" dir="userguide/tutorial/selectProject" title="Selecting the project using a build file">
             <sourcefile file="subdir/myproject.gradle"/>
             <output args="-q -b subdir/myproject.gradle hello"/>
         </sample>
-        <para>Alternatively, you can use the <option>-p</option> option to specify the project directory to use:</para>
+        <para>Alternatively, you can use the <option>-p</option> option to specify the project directory to use.
+        For multi-project builds you should use <option>-p</option> option instead of <option>-b</option> option.
+        </para>
         <sample id="selectProjectUsingProjectDir" dir="userguide/tutorial/selectProject" title="Selecting the project using project directory">
             <output args="-q -p subdir hello"/>
         </sample>
diff --git a/subprojects/docs/src/docs/userguide/depMngmt.xml b/subprojects/docs/src/docs/userguide/depMngmt.xml
index 5ca081f..aae9839 100644
--- a/subprojects/docs/src/docs/userguide/depMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/depMngmt.xml
@@ -637,6 +637,9 @@
             <sample id="mavenLocalRepo" dir="userguide/artifacts/defineRepository" title="Adding the local Maven cache as a repository">
                 <sourcefile file="build.gradle" snippet="maven-local"/>
             </sample>
+            <para>Gradle uses the same logic as maven to identify the location of your local maven cache. If a local repository location is defined in a <filename>settings.xml</filename>, this location will be used.
+                The <filename>settings.xml</filename> in <filename><replaceable>USER_HOME</replaceable>/.m2</filename> takes precedence over the <filename>settings.xml</filename> in <filename><replaceable>M2_HOME</replaceable>/conf</filename>.
+                If no <filename>settings.xml</filename> is available, Gradle uses the default location <filename><replaceable>USER_HOME</replaceable>/.m2/repository</filename>.</para>
         </section>
 
         <section id='sub:maven_repo'>
diff --git a/subprojects/docs/src/docs/userguide/earPlugin.xml b/subprojects/docs/src/docs/userguide/earPlugin.xml
index c4642f3..a781e06 100644
--- a/subprojects/docs/src/docs/userguide/earPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/earPlugin.xml
@@ -90,9 +90,9 @@
         <title>Dependency management</title>
         <para>The Ear plugin adds two dependency configurations: <literal>deploy</literal> and
             <literal>earlib</literal>. All dependencies in the <literal>deploy</literal> configuration are
-            placed in the root of the EAR archive, and are <em>not</em> transitive. All dependencies in the
+            placed in the root of the EAR archive, and are <emphasis>not</emphasis> transitive. All dependencies in the
             <literal>earlib</literal> configuration are placed in the 'lib' directory in the EAR archive and
-            <em>are</em> transitive.
+            <emphasis>are</emphasis> transitive.
         </para>
     </section>
 
diff --git a/subprojects/docs/src/docs/userguide/embedding.xml b/subprojects/docs/src/docs/userguide/embedding.xml
index be227e2..14a6b73 100644
--- a/subprojects/docs/src/docs/userguide/embedding.xml
+++ b/subprojects/docs/src/docs/userguide/embedding.xml
@@ -72,7 +72,7 @@
         <para>The Tooling API is the official and recommended way to embed Gradle.
             This means that the existing APIs, namely <apilink class="org.gradle.GradleLauncher"/>
             and the open API (the UIFactory and friends),
-            are mildly deprecated and will be removed in some future version of Gradle.
+            are deprecated and will be removed in some future version of Gradle.
             If you happen to use one of the above APIs, please consider changing your application to use the tooling API instead.
         </para>
     </section>
@@ -89,7 +89,7 @@
 
     <section id='sec:Quickstart'>
         <title>Quickstart</title>
-        <para>Since the tooling API is an interface for a programmer most of the documentation lives in Javadoc/Groovydoc.
+        <para>Since the tooling API is an interface for a programmer most of the documentation lives in the Javadoc.
             This is exactly our intention - we don't expect this chapter to grow very much.
             Instead we will add more code samples and improve the Javadoc documentation.
             The main entry point to the tooling API is the
diff --git a/subprojects/docs/src/docs/userguide/groovyTutorial.xml b/subprojects/docs/src/docs/userguide/groovyTutorial.xml
index 61a1690..e1349c7 100644
--- a/subprojects/docs/src/docs/userguide/groovyTutorial.xml
+++ b/subprojects/docs/src/docs/userguide/groovyTutorial.xml
@@ -32,15 +32,15 @@
         <para>This will also apply the Java plugin to the project, if it has not already been applied. The Groovy plugin
             extends the <literal>compile</literal> task to look for source files in directory
             <filename>src/main/groovy</filename>, and the <literal>compileTest</literal> task to look for test source
-            files in directory<filename>src/test/groovy</filename>. The compile tasks use joint compilation for these
+            files in directory <filename>src/test/groovy</filename>. The compile tasks use joint compilation for these
             directories, which means they can contain a mixture of java and groovy source files.
         </para>
         <para>To use the groovy compilation tasks, you must also declare the Groovy version to use and where to find the
             Groovy libraries. You do this by adding a dependency to the <literal>groovy</literal> configuration.
             The <literal>compile</literal> configuration inherits this dependency, so the groovy libraries will
-            be included in classpath when compiling Groovy and Java source.  For our sample, we will use Groovy 1.6.0
+            be included in classpath when compiling Groovy and Java source.  For our sample, we will use Groovy 1.7.10
             from the public Maven repository:</para>
-        <sample id="groovyQuickstart" dir="groovy/quickstart" title="Dependency on Groovy 1.6.0">
+        <sample id="groovyQuickstart" dir="groovy/quickstart" title="Dependency on Groovy 1.7.10">
             <sourcefile file="build.gradle" snippet="groovy-dependency"/>
         </sample>
         <para>Here is our complete build file:</para>
diff --git a/subprojects/docs/src/docs/userguide/javaPlugin.xml b/subprojects/docs/src/docs/userguide/javaPlugin.xml
index ccec467..d92a73a 100644
--- a/subprojects/docs/src/docs/userguide/javaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/javaPlugin.xml
@@ -1143,6 +1143,11 @@
                 Test always executes every test that it detects. It stops the build afterwards if <literal>ignoreFailures</literal>
                 is false and there are failing tests. The default value of <literal>ignoreFailures</literal> is false.
             </para>
+            <para>The <literal>testLogging</literal> property allows to configure which test events are going to be logged and at
+                which detail level. By default, a concise message will be logged for every failed test. See
+                <apilink class="org.gradle.api.tasks.testing.logging.TestLoggingContainer"/> for how to tune test logging to your
+                preferences.
+            </para>
         </section>
 
         <section>
diff --git a/subprojects/docs/src/docs/userguide/mavenPlugin.xml b/subprojects/docs/src/docs/userguide/mavenPlugin.xml
index 0505b1d..ec8c526 100644
--- a/subprojects/docs/src/docs/userguide/mavenPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/mavenPlugin.xml
@@ -298,25 +298,65 @@
         </section>
         <section id='sec:maven_pom_generation'>
             <title>Maven POM generation</title>
-            <para>The Maven POMs for uploading are automatically generated by Gradle. The groupId, artifactId, version and packaging
-                values are taken from the project object. The dependency elements are created from the Gradle dependency declarations.
-                You can find the generated POMs in the directory
-                <literal><buildDir>/poms</literal>. You can further customize the POM via the API of the
-                <apilink class="org.gradle.api.artifacts.maven.MavenPom"/> object.
+            <para>When deploying an artifact to a Maven repository, Gradle automatically generates a POM for it. The
+                <literal>groupId</literal>, <literal>artifactId</literal>, <literal>version</literal> and <literal>packaging</literal>
+                elements used for the POM default to the values shown in the table below. The <literal>dependency</literal>
+                elements are created from the project's dependency declarations.
             </para>
-            <para>You might want the artifact deployed to the maven repository to have a different version or name than
-                the artifact generated by Gradle. To customize these you can do:
+            <table>
+                <title>Default Values for Maven POM generation</title>
+                <thead>
+                    <tr>
+                        <td>Maven Element</td>
+                        <td>Default Value</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td>groupId</td>
+                    <td>project.group</td>
+                </tr>
+                <tr>
+                    <td>artifactId</td>
+                    <td>uploadTask.repositories.mavenDeployer.pom.artifactId (if set) or archiveTask.baseName.</td>
+                </tr>
+                <tr>
+                    <td>version</td>
+                    <td>project.version</td>
+                </tr>
+                <tr>
+                    <td>packaging</td>
+                    <td>archiveTask.extension</td>
+                </tr>
+            </table>
+            <para>
+                Here, <literal>uploadTask</literal> and <literal>archiveTask</literal> refer to the tasks used for uploading and generating the archive,
+                respectively (for example <literal>uploadArchives</literal> and <literal>jar</literal>). <literal>archiveTask.baseName</literal> defaults
+                to <literal>project.archivesBaseName</literal> which in turn defaults to <literal>project.name</literal>.
+            </para>
+            <note>
+                <para>
+                    When you set <literal>archiveTask.baseName</literal> to a value other than the default, make sure to set
+                    <literal>uploadTask.repositories.mavenDeployer.pom.artifactId</literal> to the same value. Otherwise, the project at hand may
+                    be referenced with the wrong artifact ID from generated POMs for other projects in the same build.
+                </para>
+            </note>
+            <para>
+                Generated POMs can be found in <literal><buildDir>/poms</literal>. They can be further customized via the
+                <apilink class="org.gradle.api.artifacts.maven.MavenPom"/> API. For example, you might want the artifact deployed
+                to the Maven repository to have a different version or name than the artifact generated by Gradle. To customize these you can do:
             </para>
             <sample id="customizePom" dir="userguide/artifacts/maven" title="Customization of pom">
                 <sourcefile file="build.gradle" snippet="customize-pom"/>
             </sample>
-            <para>Or you want to add new elements like license information.</para>
+            <para>To add additional content to the POM, the <literal>pom.project</literal> builder can be used. With this builder,
+                any element listed in the <ulink url="http://maven.apache.org/pom.html">Maven POM reference</ulink> can be added.
+            </para>
             <sample id="pomBuilder" dir="userguide/artifacts/maven" title="Builder style customization of pom">
                 <sourcefile file="build.gradle" snippet="builder"/>
             </sample>
-            <para>We use a builder here. You could also add the artifactId and groupId via the builder.</para>
-            <para>The pom object offers a <literal>whenConfigure</literal> method, if you need to modify the
-                autogenerated content.</para>
+            <para>Note: <literal>groupId</literal>, <literal>artifactId</literal>, <literal>version</literal>, and <literal>packaging</literal>
+                should always be set directly on the <literal>pom</literal> object.
+            </para>
             <sample id="pomBuilder" dir="maven/pomGeneration" title="Modifying auto-generated content">
                 <sourcefile file="build.gradle" snippet="when-configured"/>
             </sample>
@@ -329,7 +369,6 @@
             <sample id="customizeInstaller" dir="userguide/artifacts/maven" title="Customization of Maven installer">
                 <sourcefile file="build.gradle" snippet="customize-installer"/>
             </sample>
-            <para>In contrast to the example above we use the builder here for changing groupId and artifactId.</para> 
             <section id='sub:multiple_artifacts_per_project'>
                 <title>Multiple artifacts per project</title>
                 <para>Maven can only deal with one artifact per project. This is reflected in the structure of the
diff --git a/subprojects/docs/src/docs/userguide/standardPlugins.xml b/subprojects/docs/src/docs/userguide/standardPlugins.xml
index 6c73a10..096e25b 100644
--- a/subprojects/docs/src/docs/userguide/standardPlugins.xml
+++ b/subprojects/docs/src/docs/userguide/standardPlugins.xml
@@ -354,7 +354,7 @@
                 <td>-</td>
                 <td>-</td>
                 <td>
-                    <para>Provides integration with the Sonar code quality platform.
+                    <para>Provides integration with the <ulink url="http://www.sonarsource.org">Sonar</ulink> code quality platform.
                     </para>
                 </td>
             </tr>
diff --git a/subprojects/docs/src/docs/userguide/userguide.xml b/subprojects/docs/src/docs/userguide/userguide.xml
index b382017..0ca2bce 100644
--- a/subprojects/docs/src/docs/userguide/userguide.xml
+++ b/subprojects/docs/src/docs/userguide/userguide.xml
@@ -18,7 +18,7 @@
         <title>Gradle User Guide</title>
         <subtitle>A better way to build</subtitle>
         <copyright>
-            <year>2007-2010</year>
+            <year>2007-2012</year>
             <holder>Hans Dockter, Adam Murdoch</holder>
         </copyright>
         <legalnotice>
@@ -48,14 +48,14 @@
     <xi:include href='webTutorial.xml'/>
     <xi:include href='commandLineTutorial.xml'/>
     <xi:include href='guiTutorial.xml'/>
-    <xi:include href='gradleDaemon.xml'/>
-    <xi:include href='thisAndThat.xml'/>
-    <xi:include href='buildEnvironment.xml'/>
     <xi:include href='writingBuildScripts.xml'/>
+    <xi:include href='thisAndThat.xml'/>
     <xi:include href='tasks.xml'/>
     <xi:include href='workingWithFiles.xml'/>
-    <xi:include href='logging.xml'/>
     <xi:include href='ant.xml'/>
+    <xi:include href='logging.xml'/>
+    <xi:include href='gradleDaemon.xml'/>
+    <xi:include href='buildEnvironment.xml'/>
     <xi:include href='plugins.xml'/>
     <xi:include href='standardPlugins.xml'/>
     <xi:include href='javaPlugin.xml'/>
diff --git a/subprojects/docs/src/docs/userguide/workingWithFiles.xml b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
index 1d70c42..1c289ee 100644
--- a/subprojects/docs/src/docs/userguide/workingWithFiles.xml
+++ b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
@@ -302,7 +302,7 @@
             </sample>
 
             <para>This adds a <classname>Zip</classname> archive task with the name <literal>myZip</literal> which produces
-                ZIP file<filename>zipProject-1.0.zip</filename>. It is important to distinguish between the name of the archive task
+                ZIP file <filename>zipProject-1.0.zip</filename>. It is important to distinguish between the name of the archive task
                 and the name of the archive generated by the archive task. The default name for archives can be
                 changed with the <literal>archivesBaseName</literal> project property. The name of the archive can also be
                 changed at any time later on.</para>
diff --git a/subprojects/docs/src/docs/userguide/writingBuildScripts.xml b/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
index 9f8c0a3..8bdeff3 100644
--- a/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
+++ b/subprojects/docs/src/docs/userguide/writingBuildScripts.xml
@@ -27,7 +27,7 @@
         <title>The Project API</title>
         <para>In the tutorial in <xref linkend='tutorial_java_projects'/> we used, for example, the
             <literal>apply()</literal> method. Where does this method come from? We said earlier that the build script
-            defines a project in Gradle. For each project in the build creates an instance of type
+            defines a project in Gradle. For each project in the build, Gradle creates an instance of type
             <apilink class='org.gradle.api.Project'/> and associates this <classname>Project</classname> object with
             the build script. As the build script executes, it configures this <classname>Project</classname> object:
         </para>
@@ -150,19 +150,22 @@
         <section id='sec:extra_properties'>
             <title>Extra properties</title>
             <para>All enhanced objects in Gradle's domain model can hold extra user-defined properties. This includes, but is not limited to, projects, tasks,
-                and source sets. Extra properties can be added, read and set via the owning object's <literal>ext</literal> property.
+                and source sets. Extra properties can be added, read and set via the owning object's <literal>ext</literal> property. Alternatively, an
+                <literal>ext</literal> block can be used to add multiple properties at once.
             </para>
             <sample id="extraProperties" dir="userguide/tutorial/extraProperties" title="Using extra properties">
-                <sourcefile file="build.gradle" snippet="sourceSetProperty"/>
+                <sourcefile file="build.gradle" snippet="extraProperties"/>
+                <output args="-q printProperties"/>
             </sample>
-            <para>In this example, a property named <literal>purpose</literal> is added to all source sets by setting <literal>ext.purpose</literal> to
-                <literal>null</literal> (<literal>null</literal> is a permissible value). Once the property has been added, it can be read and set like
-                a predefined property. Alternatively, <literal>ext.</literal> can be used as well.
+            <para>In this example, an <literal>ext</literal> block adds two extra properties to the <literal>project</literal> object. Additionally,
+                a property named <literal>purpose</literal> is added to each source set by setting <literal>ext.purpose</literal> to
+                <literal>null</literal> (<literal>null</literal> is a permissible value). Once the properties have been added, they can be read and set like
+                predefined properties.
             </para>
             <para>By requiring special syntax for adding a property, Gradle can fail fast when an attempt is made to set a (predefined or extra) property
                 but the property is misspelled or does not exist.
                 <footnote>
-                    <para>As of Gradle 1.0 milestone 9, using <literal>ext</literal> to add extra properties is strongly encouraged but not yet enforced.
+                    <para>As of Gradle 1.0-milestone-9, using <literal>ext</literal> to add extra properties is strongly encouraged but not yet enforced.
                         Therefore, Gradle will not fail when an unknown property is set. However, it will print a warning.
                     </para>
                 </footnote>
diff --git a/subprojects/docs/src/samples/announce/build.gradle b/subprojects/docs/src/samples/announce/build.gradle
index 7b47911..292e22c 100644
--- a/subprojects/docs/src/samples/announce/build.gradle
+++ b/subprojects/docs/src/samples/announce/build.gradle
@@ -15,12 +15,12 @@ announce {
 
 //START SNIPPET announce-usage
 task helloWorld << {  
-    ant.echo(message: "hello") 
+    println "Hello, world!"
 }  
 
 helloWorld.doLast {  
-    announce("Build complete", "notify-send")
-    announce("Build complete", "twitter")
+    announce.announce("helloWorld completed!", "twitter")
+    announce.announce("helloWorld completed!", "local")
 }
 //END SNIPPET announce-usage
 
diff --git a/subprojects/docs/src/samples/customDistribution/plugin/build.gradle b/subprojects/docs/src/samples/customDistribution/plugin/build.gradle
index 6739273..f046720 100644
--- a/subprojects/docs/src/samples/customDistribution/plugin/build.gradle
+++ b/subprojects/docs/src/samples/customDistribution/plugin/build.gradle
@@ -13,7 +13,7 @@ configurations {
 dependencies {
     gradleApi gradleApi()
     groovy localGroovy()
-    compile 'com.google.guava:guava:11.0.1'
+    compile 'com.google.guava:guava:11.0.2'
 }
 
 task dist(type: Zip) {
diff --git a/subprojects/docs/src/samples/customPlugin/consumer/build.gradle b/subprojects/docs/src/samples/customPlugin/consumer/build.gradle
index fe39333..33e1024 100644
--- a/subprojects/docs/src/samples/customPlugin/consumer/build.gradle
+++ b/subprojects/docs/src/samples/customPlugin/consumer/build.gradle
@@ -11,9 +11,6 @@ buildscript {
     }
 }
 // END SNIPPET use-task
-buildscript {
-   configurations.classpath.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
 apply plugin: 'greeting'
 // END SNIPPET use-plugin
 // START SNIPPET use-task
diff --git a/subprojects/docs/src/samples/ivypublish/build.gradle b/subprojects/docs/src/samples/ivypublish/build.gradle
index 79a97a1..32ea2e7 100644
--- a/subprojects/docs/src/samples/ivypublish/build.gradle
+++ b/subprojects/docs/src/samples/ivypublish/build.gradle
@@ -41,7 +41,7 @@ uploadArchives {
     }
     doLast {
         File repoDir = new File("$buildDir/repo/ivypublish/1.0/")
-        assert ["ivypublish.jar", "ivy.xml", "ivypublishSource.jar"] as Set == repoDir.listFiles().collect { it.name } as Set
+        assert ["ivy.xml", "ivy.xml.sha1", "ivypublish.jar", "ivypublish.jar.sha1", "ivypublishSource.jar", "ivypublishSource.jar.sha1"] as Set == repoDir.listFiles().collect { it.name } as Set
         assert jar.archivePath.size() == new File(repoDir, 'ivypublish.jar').size()
         sourceJar.archivePath.size() == new File(repoDir, 'ivypublishSource.jar').size()
 
diff --git a/subprojects/docs/src/samples/osgi/build.gradle b/subprojects/docs/src/samples/osgi/build.gradle
index 6144f06..dba106b 100644
--- a/subprojects/docs/src/samples/osgi/build.gradle
+++ b/subprojects/docs/src/samples/osgi/build.gradle
@@ -20,7 +20,7 @@ dependencies {
 
 jar {
     manifest {
-        version = '1.0'
+        version = '1.0.0'
         name = 'Example Gradle Activator'
         instruction 'Bundle-Activator', 'org.gradle.GradleActivator'
         instruction 'Import-Package', '*'
diff --git a/subprojects/docs/src/samples/toolingApi/build/build.gradle b/subprojects/docs/src/samples/toolingApi/build/build.gradle
index 2a6a867..053888e 100644
--- a/subprojects/docs/src/samples/toolingApi/build/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/build/build.gradle
@@ -1,19 +1,11 @@
-//This build script contains extra logic that we use for automated tests
-//Not all of that is needed to use the Tooling Api!
-
 apply plugin: 'java'
 apply plugin: 'application'
 
-if (!hasProperty('toolingApiVersion')) {
-    ext.toolingApiVersion = gradle.gradleVersion
-}
-if (!hasProperty('toolingApiRepo')) {
-    ext.toolingApiRepo = 'http://repo.gradle.org/gradle/libs-releases-local'
-}
+def toolingApiVersion = gradle.gradleVersion
 
 repositories {
     maven {
-        url toolingApiRepo
+        url 'http://repo.gradle.org/gradle/libs-releases-local'
     }
     mavenCentral()
 }
@@ -21,18 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.4'
+    runtime 'org.slf4j:slf4j-simple:1.6.6'
 }
 
 mainClassName = 'org.gradle.sample.Main'
-
-run {
-    if (project.hasProperty('gradleDistribution')) {
-        args = [gradleDistribution]
-    }
-
-    if (project.hasProperty('automationSystemProperty')) {
-        def entry = project.getProperty('automationSystemProperty').split('=')
-        systemProperties[entry[0]] = entry[1]
-    }
-}
diff --git a/subprojects/docs/src/samples/toolingApi/build/readme.xml b/subprojects/docs/src/samples/toolingApi/build/readme.xml
index 6a5484f..27cf4bd 100644
--- a/subprojects/docs/src/samples/toolingApi/build/readme.xml
+++ b/subprojects/docs/src/samples/toolingApi/build/readme.xml
@@ -1,3 +1,3 @@
 <sample>
-    <para>An application which uses the tooling API to execute a Gradle build.</para>
+    <para>An application that uses the tooling API to run a Gradle task.</para>
 </sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
index 2a6a867..053888e 100644
--- a/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/eclipse/build.gradle
@@ -1,19 +1,11 @@
-//This build script contains extra logic that we use for automated tests
-//Not all of that is needed to use the Tooling Api!
-
 apply plugin: 'java'
 apply plugin: 'application'
 
-if (!hasProperty('toolingApiVersion')) {
-    ext.toolingApiVersion = gradle.gradleVersion
-}
-if (!hasProperty('toolingApiRepo')) {
-    ext.toolingApiRepo = 'http://repo.gradle.org/gradle/libs-releases-local'
-}
+def toolingApiVersion = gradle.gradleVersion
 
 repositories {
     maven {
-        url toolingApiRepo
+        url 'http://repo.gradle.org/gradle/libs-releases-local'
     }
     mavenCentral()
 }
@@ -21,18 +13,7 @@ repositories {
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.4'
+    runtime 'org.slf4j:slf4j-simple:1.6.6'
 }
 
 mainClassName = 'org.gradle.sample.Main'
-
-run {
-    if (project.hasProperty('gradleDistribution')) {
-        args = [gradleDistribution]
-    }
-
-    if (project.hasProperty('automationSystemProperty')) {
-        def entry = project.getProperty('automationSystemProperty').split('=')
-        systemProperties[entry[0]] = entry[1]
-    }
-}
diff --git a/subprojects/docs/src/samples/toolingApi/eclipse/readme.xml b/subprojects/docs/src/samples/toolingApi/eclipse/readme.xml
index afe569d..58a0358 100644
--- a/subprojects/docs/src/samples/toolingApi/eclipse/readme.xml
+++ b/subprojects/docs/src/samples/toolingApi/eclipse/readme.xml
@@ -1,3 +1,3 @@
 <sample>
-    <para>An application which uses the tooling API to build the Eclipse model for a project.</para>
+    <para>An application that uses the tooling API to build the Eclipse model for a project.</para>
 </sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/idea/build.gradle b/subprojects/docs/src/samples/toolingApi/idea/build.gradle
index c41c9ba..053888e 100644
--- a/subprojects/docs/src/samples/toolingApi/idea/build.gradle
+++ b/subprojects/docs/src/samples/toolingApi/idea/build.gradle
@@ -1,45 +1,19 @@
-//This build script contains extra logic that we use for automated tests
-//Not all of that is needed to use the Tooling Api!
-
 apply plugin: 'java'
 apply plugin: 'application'
 
-if (!hasProperty('toolingApiVersion')) {
-    ext.toolingApiVersion = gradle.gradleVersion
-}
-if (!hasProperty('toolingApiRepo')) {
-    ext.toolingApiRepo = 'http://repo.gradle.org/gradle/libs-releases-local'
-}
+def toolingApiVersion = gradle.gradleVersion
 
 repositories {
     maven {
-        url toolingApiRepo
+        url 'http://repo.gradle.org/gradle/libs-releases-local'
     }
     mavenCentral()
 }
 
-mainClassName = 'org.gradle.sample.Main'
-
 dependencies {
     compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
     // Need an SLF4J implementation at runtime
-    runtime 'org.slf4j:slf4j-simple:1.6.4'
+    runtime 'org.slf4j:slf4j-simple:1.6.6'
 }
 
 mainClassName = 'org.gradle.sample.Main'
-
-run {
-    if (project.hasProperty('gradleDistribution')) {
-        args = [gradleDistribution]
-    }
-
-    if (project.hasProperty('automationSystemProperty')) {
-        def entry = project.getProperty('automationSystemProperty').split('=')
-        systemProperties[entry[0]] = entry[1]
-    }
-    
-    //jvmArgs = ['-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']
-}
-
-//Example for local debugging:
-//gradle clean run -u -PtoolingApiRepo=/Users/szczepan/gradle/gradle.src/build/repo -PgradleDistribution=/Users/szczepan/programs/gradle-current -PtoolingApiVersion=1.0-milestone-4-20110725164259+0200
diff --git a/subprojects/docs/src/samples/toolingApi/idea/readme.xml b/subprojects/docs/src/samples/toolingApi/idea/readme.xml
index 8a94816..d4d9356 100644
--- a/subprojects/docs/src/samples/toolingApi/idea/readme.xml
+++ b/subprojects/docs/src/samples/toolingApi/idea/readme.xml
@@ -1,19 +1,3 @@
-<!--
-  ~ Copyright 2011 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.
-  -->
-
 <sample>
-    <para>An application which uses the tooling API to extract information needed by IntelliJ IDEA.</para>
+    <para>An application that uses the tooling API to extract information needed by IntelliJ IDEA.</para>
 </sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/model/build.gradle b/subprojects/docs/src/samples/toolingApi/model/build.gradle
new file mode 100644
index 0000000..053888e
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/model/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'java'
+apply plugin: 'application'
+
+def toolingApiVersion = gradle.gradleVersion
+
+repositories {
+    maven {
+        url 'http://repo.gradle.org/gradle/libs-releases-local'
+    }
+    mavenCentral()
+}
+
+dependencies {
+    compile "org.gradle:gradle-tooling-api:${toolingApiVersion}"
+    // Need an SLF4J implementation at runtime
+    runtime 'org.slf4j:slf4j-simple:1.6.6'
+}
+
+mainClassName = 'org.gradle.sample.Main'
diff --git a/subprojects/docs/src/samples/toolingApi/model/readme.xml b/subprojects/docs/src/samples/toolingApi/model/readme.xml
new file mode 100644
index 0000000..b54c462
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/model/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>An application that uses the tooling API to build the model for a Gradle build.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/toolingApi/model/src/main/java/org/gradle/sample/Main.java b/subprojects/docs/src/samples/toolingApi/model/src/main/java/org/gradle/sample/Main.java
new file mode 100644
index 0000000..20408fe
--- /dev/null
+++ b/subprojects/docs/src/samples/toolingApi/model/src/main/java/org/gradle/sample/Main.java
@@ -0,0 +1,33 @@
+package org.gradle.sample;
+
+import org.gradle.tooling.*;
+import org.gradle.tooling.model.*;
+import org.gradle.tooling.model.eclipse.*;
+
+import java.io.File;
+import java.lang.Object;
+
+public class Main {
+    public static void main(String[] args) {
+        // Configure the connector and create the connection
+        GradleConnector connector = GradleConnector.newConnector();
+        connector.forProjectDirectory(new File("."));
+        if (args.length > 0) {
+            connector.useInstallation(new File(args[0]));
+        }
+
+        ProjectConnection connection = connector.connect();
+        try {
+            // Load the model for the project
+            GradleProject project = connection.getModel(GradleProject.class);
+            System.out.println("Project: " + project.getName());
+            System.out.println("Tasks:");
+            for (Task task : project.getTasks()) {
+                System.out.println("    " + task.getName());
+            }
+        } finally {
+            // Clean up
+            connection.close();
+        }
+    }
+}
diff --git a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
index 2cba162..0218548 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
@@ -45,7 +45,7 @@ dependencies {
 
 //START SNIPPET dependencies-with-empty-attributes
 dependencies {
-    runtime ":junit:4.8.2", ":testng"
+    runtime ":junit:4.10", ":testng"
     runtime name: 'testng' 
 }
 //END SNIPPET dependencies-with-empty-attributes
@@ -59,19 +59,19 @@ dependencies {
 
 //START SNIPPET artifact-only
 dependencies {
-	runtime "org.groovy:groovy:1.5.6 at jar"
-    runtime group: 'org.groovy', name: 'groovy', version: '1.5.6', ext: 'jar'
+	runtime "org.groovy:groovy:1.8.6 at jar"
+    runtime group: 'org.groovy', name: 'groovy', version: '1.8.6', ext: 'jar'
 }
 //END SNIPPET artifact-only
 
 //START SNIPPET client-modules
 dependencies {
-    runtime module("org.codehaus.groovy:groovy-all:1.8.4") {
+    runtime module("org.codehaus.groovy:groovy-all:1.8.6") {
         dependency("commons-cli:commons-cli:1.0") {
             transitive = false
         }
-        module(group: 'org.apache.ant', name: 'ant', version: '1.8.2') {
-            dependencies "org.apache.ant:ant-launcher:1.8.2 at jar", "org.apache.ant:ant-junit:1.8.2"
+        module(group: 'org.apache.ant', name: 'ant', version: '1.8.4') {
+            dependencies "org.apache.ant:ant-launcher:1.8.4 at jar", "org.apache.ant:ant-junit:1.8.4"
         }
     }
 }
@@ -85,9 +85,9 @@ dependencies {
 //END SNIPPET file-dependencies
 
 //START SNIPPET list-grouping
-List groovy = ["org.codehaus.groovy:groovy-all:1.8.4 at jar",
+List groovy = ["org.codehaus.groovy:groovy-all:1.8.6 at jar",
                "commons-cli:commons-cli:1.0 at jar",
-               "org.apache.ant:ant:1.8.2 at jar"]
+               "org.apache.ant:ant:1.8.4 at jar"]
 List hibernate = ['org.hibernate:hibernate:3.0.5 at jar', 'somegroup:someorg:1.0 at jar']
 dependencies {
 	runtime groovy, hibernate
diff --git a/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
index 643cc40..5c801d8 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/maven/build.gradle
@@ -70,10 +70,10 @@ uploadArchives {
 //END SNIPPET upload-with-ssh
 
 //START SNIPPET customize-installer
-configure(install.repositories.mavenInstaller) {
-    pom.project {
-        version '1.0Maven'
-        artifactId 'myName'
+install {
+    repositories.mavenInstaller {
+        pom.version = '1.0Maven'
+        pom.artifactId = 'myName'
     }
 }
 //END SNIPPET customize-installer
diff --git a/subprojects/docs/src/samples/userguide/organizeBuildLogic/build.gradle b/subprojects/docs/src/samples/userguide/organizeBuildLogic/build.gradle
index 4528f60..8917df9 100644
--- a/subprojects/docs/src/samples/userguide/organizeBuildLogic/build.gradle
+++ b/subprojects/docs/src/samples/userguide/organizeBuildLogic/build.gradle
@@ -3,7 +3,7 @@ configurations {
 }
 
 dependencies {
-    ftpAntTask("org.apache.ant:ant-commons-net:1.8.2") {
+    ftpAntTask("org.apache.ant:ant-commons-net:1.8.4") {
         module("commons-net:commons-net:1.4.1") {
             dependencies "oro:oro:2.0.8:jar"
         }
diff --git a/subprojects/docs/src/samples/userguide/tutorial/extraProperties/build.gradle b/subprojects/docs/src/samples/userguide/tutorial/extraProperties/build.gradle
index f9e3a94..d349213 100644
--- a/subprojects/docs/src/samples/userguide/tutorial/extraProperties/build.gradle
+++ b/subprojects/docs/src/samples/userguide/tutorial/extraProperties/build.gradle
@@ -1,15 +1,21 @@
-// START SNIPPET taskProperty
-task myTask
-myTask.ext.myProperty = 'myCustomPropValue'
+// START SNIPPET taskProperties
+task myTask {
+    ext.myProperty = "myValue"
+}
 
-task showProps << {
+task printTaskProperties << {
     println myTask.myProperty
 }
-// END SNIPPET taskProperty
+// END SNIPPET taskProperties
 
-// START SNIPPET sourceSetProperty
+// START SNIPPET extraProperties
 apply plugin: "java"
 
+ext {
+    springVersion = "3.1.0.RELEASE"
+    emailNotification = "build at master.org"
+}
+
 sourceSets.all { ext.purpose = null }
 
 sourceSets {
@@ -20,11 +26,13 @@ sourceSets {
         purpose = "test"
     }
     plugin {
-        ext.purpose = "production"
+        purpose = "production"
     }
 }
 
-task printProductionSourceDirs << {
-    sourceSets.matching { it.purpose == "production" }.each { println it.java.srcDirs }
+task printProperties << {
+    println springVersion
+    println emailNotification
+    sourceSets.matching { it.purpose == "production" }.each { println it.name }
 }
-// END SNIPPET sourceSetProperty
\ No newline at end of file
+// END SNIPPET extraProperties
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/extraProperties.out b/subprojects/docs/src/samples/userguideOutput/extraProperties.out
new file mode 100644
index 0000000..7d55e2f
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/extraProperties.out
@@ -0,0 +1,4 @@
+3.1.0.RELEASE
+build at master.org
+main
+plugin
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguideOutput/extraTaskProperties.out b/subprojects/docs/src/samples/userguideOutput/extraTaskProperties.out
index 32b8a4d..1a24a94 100644
--- a/subprojects/docs/src/samples/userguideOutput/extraTaskProperties.out
+++ b/subprojects/docs/src/samples/userguideOutput/extraTaskProperties.out
@@ -1 +1 @@
-myCustomPropValue
+myValue
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
index cd27c69..d97a0f7 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/EarPlugin.java
@@ -23,7 +23,7 @@ import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
 import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
 import org.gradle.api.internal.project.ProjectInternal;
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
index aec98a9..69a6703 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathIntegrationTest.groovy
@@ -628,7 +628,7 @@ dependencies {
         def libraries = classpath.libs
         assert libraries.size() == 3
         libraries[0].assertHasJar(repoJar)
-        libraries[1].assertHasJar(file('unresolved dependency - i.dont#Exist;1.0'))
+        libraries[1].assertHasJar(file('unresolved dependency - i.dont Exist 1.0'))
         libraries[2].assertHasJar(localJar)
     }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
index a343841..ccdf7e4 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
@@ -69,8 +69,4 @@ eclipse {
 //        then:
         result.error == ''
     }
-
-    def cleanup() {
-        server.resetExpectations()
-    }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
index 357c546..0cfb09f 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
@@ -17,7 +17,6 @@
 package org.gradle.plugins.ide.idea
 
 import java.util.regex.Pattern
-import junit.framework.AssertionFailedError
 import org.custommonkey.xmlunit.Diff
 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
 import org.custommonkey.xmlunit.XMLAssert
@@ -349,13 +348,13 @@ apply plugin: "idea"
         diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
         try {
             XMLAssert.assertXMLEqual(diff, true)
-        } catch (AssertionFailedError e) {
+        } catch (AssertionError e) {
             if (OperatingSystem.current().unix) {
                 def process = ["diff", expectedFile.absolutePath, file.absolutePath].execute()
                 process.consumeProcessOutput(System.out, System.err)
                 process.waitFor()
             }
-            throw new AssertionFailedError("generated file '$path' does not contain the expected contents: ${e.message}.\nExpected:\n${expectedXml}\nActual:\n${actualXml}").initCause(e)
+            throw new AssertionError("generated file '$path' does not contain the expected contents: ${e.message}.\nExpected:\n${expectedXml}\nActual:\n${actualXml}").initCause(e)
         }
     }
 
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
index f4ef078..ffad1da 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
@@ -252,6 +252,85 @@ idea.module {
     }
 
     @Test
+    void "respects external dependencies order"() {
+        //given
+        def repoDir = file("repo")
+        maven(repoDir).module("org.gradle", "artifact1").publish()
+        maven(repoDir).module("org.gradle", "artifact2").publish()
+
+        //when
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+    maven { url "${repoDir.toURI()}" }
+}
+
+dependencies {
+    compile 'org.gradle:artifact1:1.0'
+    compile 'org.gradle:artifact2:1.0'
+}
+"""
+        def content = getFile([:], 'root.iml').text
+
+        //then
+        def a1 = content.indexOf("artifact1")
+        def a2 = content.indexOf("artifact2")
+
+        assert [a1, a2] == [a1, a2].sort()
+    }
+
+    @Test
+    void "respects local dependencies order"() {
+        //given
+        file('artifact1.jar').createNewFile()
+        file('artifact2.jar').createNewFile()
+
+        //when
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+dependencies {
+    compile files('artifact1.jar')
+    compile files('artifact2.jar')
+}
+"""
+        def content = getFile([:], 'root.iml').text
+
+        //then
+        def a1 = content.indexOf("artifact1")
+        def a2 = content.indexOf("artifact2")
+
+        assert [a1, a2] == [a1, a2].sort()
+    }
+
+    @Test
+    void "works with artifacts without group and version"() {
+        //given
+        testFile('repo/hibernate-core.jar').createFile()
+
+        //when
+        runIdeaTask """
+apply plugin: 'java'
+apply plugin: 'idea'
+
+repositories {
+  flatDir dirs: 'repo'
+}
+
+dependencies {
+    compile ':hibernate-core:'
+}
+"""
+        def content = getFile([:], 'root.iml').text
+
+        //then
+        content.contains 'hibernate-core.jar'
+    }
+
+    @Test
     void doesNotBreakWhenSomeDependenciesCannotBeResolved() {
         //given
         def repoDir = file("repo")
@@ -286,6 +365,6 @@ project(':impl') {
         assert content.count("someDependency.jar") == 1
         assert content.count("artifactTwo-1.0.jar") == 1
         assert content.count("someApiProject") == 1
-        assert content.count("unresolved dependency - i.dont#Exist;1.0") == 1
+        assert content.count("unresolved dependency - i.dont Exist 1.0") == 1
     }
 }
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
index 6cb8d1d..226833a 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
@@ -28,7 +28,7 @@ class IdeaProjectIntegrationTest extends AbstractIdeIntegrationTest {
 
     @Issue("GRADLE-1011")
     @Test
-    void "uses java plugin compatibility settings"() {
+    void "uses java plugin source compatibility settings"() {
         //when
         runTask('idea', '''
 apply plugin: "java"
@@ -40,7 +40,6 @@ sourceCompatibility = 1.4
         //then
         def ipr = getFile([:], 'root.ipr').text
 
-        assert ipr.contains('project-jdk-name="1.4"')
         assert ipr.contains('languageLevel="JDK_1_4"')
     }
 
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle
index b85fec0..8ea2b9c 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle
@@ -4,6 +4,8 @@ allprojects {
     apply plugin: 'idea'
 }
 
+idea.project.jdkName = '1.6'
+
 subprojects {
     apply plugin: 'java'
 
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithASubProjectThatDoesNotHaveTheIdeaPluginApplied/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithASubProjectThatDoesNotHaveTheIdeaPluginApplied/build.gradle
index 03ed6d5..739b340 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithASubProjectThatDoesNotHaveTheIdeaPluginApplied/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithASubProjectThatDoesNotHaveTheIdeaPluginApplied/build.gradle
@@ -1,4 +1,5 @@
 apply plugin: 'idea'
+idea.project.jdkName = '1.6'
 project('a') {
     apply plugin: 'idea'
 }
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithAnEmptyProject/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithAnEmptyProject/build.gradle
index 4475bd5..cd5d5fc 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithAnEmptyProject/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithAnEmptyProject/build.gradle
@@ -1 +1,2 @@
 apply plugin: 'idea'
+idea.project.jdkName = 1.6
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/expectedFiles/root/root.ipr.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/expectedFiles/root/root.ipr.xml
index 67b3c8f..4963b1b 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/expectedFiles/root/root.ipr.xml
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/expectedFiles/root/root.ipr.xml
@@ -49,7 +49,7 @@
       <module fileurl="file://$PROJECT_DIR$/../a child project/a child.iml" filepath="$PROJECT_DIR$/../a child project/a child.iml"/>
     </modules>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-type="JavaSDK" assert-jdk-15="true" project-jdk-name="1.5">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-type="JavaSDK" assert-jdk-15="true" project-jdk-name="1.7">
     <output url="file://$PROJECT_DIR$/out"/>
   </component>
   <component name="SvnBranchConfigurationManager">
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/root/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/root/build.gradle
index 400052e..a7280b6 100644
--- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/root/build.gradle
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/worksWithNonStandardLayout/root/build.gradle
@@ -4,3 +4,5 @@ allprojects {
 
     sourceCompatibility = 1.5
 }
+
+idea.project.jdkName = '1.7'
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipsePlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipsePlugin.groovy
index 76a212a..2293209 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipsePlugin.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipsePlugin.groovy
@@ -16,7 +16,7 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.api.Project
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.plugins.GroovyBasePlugin
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPlugin.groovy
index 323b899..f4a5101 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPlugin.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpPlugin.groovy
@@ -19,7 +19,7 @@ package org.gradle.plugins.ide.eclipse
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
 import org.gradle.api.artifacts.ProjectDependency
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.plugins.WarPlugin
 import org.gradle.plugins.ear.EarPlugin
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseJdt.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseJdt.groovy
index aed3fb4..499be65 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseJdt.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseJdt.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ide.api.PropertiesFileContentMerger
 import org.gradle.plugins.ide.api.PropertiesGeneratorTask
 import org.gradle.plugins.ide.eclipse.model.EclipseJdt
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
index aecdd3e..784f13b 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseProject.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.api.XmlGeneratorTask
 import org.gradle.plugins.ide.eclipse.model.EclipseProject
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
index f74795c..4afadc0 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpComponent.groovy
@@ -16,7 +16,7 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.api.artifacts.Configuration
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.api.XmlGeneratorTask
 import org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
index 6487e01..e45956e 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/GenerateEclipseWtpFacet.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse
 
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.api.XmlGeneratorTask
 import org.gradle.plugins.ide.eclipse.model.EclipseWtpFacet
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
index 4cdbf14..2bb9703 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/AbstractLibrary.groovy
@@ -15,6 +15,8 @@
  */
 package org.gradle.plugins.ide.eclipse.model
 
+import org.gradle.api.Nullable
+import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.plugins.ide.eclipse.model.internal.FileReferenceFactory
 
 /**
@@ -25,6 +27,8 @@ abstract class AbstractLibrary extends AbstractClasspathEntry {
     FileReference javadocPath
     FileReference library
     String declaredConfigurationName
+    @Nullable
+    ModuleVersionIdentifier moduleVersion
 
     AbstractLibrary(Node node, FileReferenceFactory fileReferenceFactory) {
         super(node)
@@ -87,6 +91,7 @@ abstract class AbstractLibrary extends AbstractClasspathEntry {
                 ", accessRules=" + accessRules +
                 ", sourcePath='" + sourcePath + '\'' +
                 ", javadocPath='" + javadocPath + '\'' +
+                ", id='" + moduleVersion + '\'' +
                 '}';
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
index fb1f35c..8b53c6c 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/ClasspathFactory.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.plugins.ide.eclipse.model.internal
 
+import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
 import org.gradle.plugins.ide.internal.IdeDependenciesExtractor.IdeLocalFileDependency
 import org.gradle.plugins.ide.internal.IdeDependenciesExtractor.IdeProjectDependency
@@ -54,12 +55,12 @@ class ClasspathFactory {
             dependenciesExtractor.extractRepoFileDependencies(
                     classpath.project.configurations, classpath.plusConfigurations, classpath.minusConfigurations, classpath.downloadSources, classpath.downloadJavadoc)
             .each { IdeRepoFileDependency it ->
-                entries << createLibraryEntry(it.file, it.sourceFile, it.javadocFile, it.declaredConfiguration.name, classpath)
+                entries << createLibraryEntry(it.file, it.sourceFile, it.javadocFile, it.declaredConfiguration.name, classpath, it.id)
             }
 
             dependenciesExtractor.extractLocalFileDependencies(classpath.plusConfigurations, classpath.minusConfigurations)
             .each { IdeLocalFileDependency it ->
-                entries << createLibraryEntry(it.file, null, null, it.declaredConfiguration.name, classpath)
+                entries << createLibraryEntry(it.file, null, null, it.declaredConfiguration.name, classpath, null)
             }
         }
     }
@@ -84,22 +85,22 @@ class ClasspathFactory {
         return entries
     }
 
-    private AbstractLibrary createLibraryEntry(File binary, File source, File javadoc, String declaredConfigurationName, EclipseClasspath classpath) {
+    private AbstractLibrary createLibraryEntry(
+            File binary, File source, File javadoc, String declaredConfigurationName, EclipseClasspath classpath,
+            ModuleVersionIdentifier id) {
         def referenceFactory = classpath.fileReferenceFactory
-        
+
         def binaryRef = referenceFactory.fromFile(binary)
         def sourceRef = referenceFactory.fromFile(source)
         def javadocRef = referenceFactory.fromFile(javadoc);
-        def out
-        if (binaryRef.relativeToPathVariable) {
-            out = new Variable(binaryRef)
-        } else {
-            out = new Library(binaryRef)
-        }
+
+        AbstractLibrary out = binaryRef.relativeToPathVariable? new Variable(binaryRef) : new Library(binaryRef)
+
         out.javadocPath = javadocRef
         out.sourcePath = sourceRef
         out.exported = true
         out.declaredConfigurationName = declaredConfigurationName
+        out.moduleVersion = id
         out
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
index 8bc19e1..0d3776c 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
@@ -18,8 +18,8 @@ package org.gradle.plugins.ide.idea;
 
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
-import org.gradle.api.internal.Instantiator
 import org.gradle.api.plugins.JavaPlugin
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.idea.internal.IdeaNameDeduper
 import org.gradle.plugins.ide.internal.IdePlugin
@@ -111,7 +111,7 @@ class IdeaPlugin extends IdePlugin {
                 model.project = ideaProject
 
                 ideaProject.outputFile = new File(project.projectDir, project.name + ".ipr")
-                ideaProject.conventionMapping.jdkName = { JavaVersion.VERSION_1_6.toString() }
+                ideaProject.conventionMapping.jdkName = { JavaVersion.current().toString() }
                 ideaProject.conventionMapping.languageLevel = { new IdeaLanguageLevel(JavaVersion.VERSION_1_6) }
                 ideaProject.wildcards = ['!?*.java', '!?*.groovy'] as Set
                 ideaProject.conventionMapping.modules = {
@@ -135,7 +135,6 @@ class IdeaPlugin extends IdePlugin {
 
     private configureIdeaProjectForJava(Project project) {
         if (isRoot(project)) {
-            project.idea.project.conventionMapping.jdkName   = { project.sourceCompatibility.toString() }
             project.idea.project.conventionMapping.languageLevel = {
                 new IdeaLanguageLevel(project.sourceCompatibility)
             }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/SingleEntryModuleLibrary.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/SingleEntryModuleLibrary.groovy
index c576f9a..7d107e9 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/SingleEntryModuleLibrary.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/SingleEntryModuleLibrary.groovy
@@ -16,15 +16,23 @@
 
 package org.gradle.plugins.ide.idea.model
 
+import org.gradle.api.Nullable
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+
 /**
  * Single entry module library
  */
 class SingleEntryModuleLibrary extends ModuleLibrary {
 
     /**
+     * Module version of the library, if any.
+     */
+    @Nullable
+    ModuleVersionIdentifier moduleVersion
+
+    /**
      * Creates single entry module library
      *
-     * @param libraryFile jar or class folder
      * @param library a path to jar or class folder in idea format
      * @param javadoc a path to javadoc jar or javadoc folder
      * @param source a path to source jar or source folder
@@ -38,7 +46,6 @@ class SingleEntryModuleLibrary extends ModuleLibrary {
     /**
      * Creates single entry module library
      *
-     * @param libraryFile jar or class folder
      * @param library a path to jar or class folder in Path format
      * @param scope scope
      * @return
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy
index 28902d8..2044150 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/internal/IdeaDependenciesProvider.groovy
@@ -72,8 +72,10 @@ class IdeaDependenciesProvider {
                     ideaModule.downloadSources, ideaModule.downloadJavadoc)
 
             repoFileDependencies.each {
-                moduleLibraries << new SingleEntryModuleLibrary(
-                    getPath(it.file), getPath(it.javadocFile), getPath(it.sourceFile), scopeName)
+                def library = new SingleEntryModuleLibrary(
+                        getPath(it.file), getPath(it.javadocFile), getPath(it.sourceFile), scopeName)
+                library.moduleVersion = it.id
+                moduleLibraries << library
             }
         }
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
index c76bb3e..a52daa0 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
@@ -39,6 +39,7 @@ class IdeDependenciesExtractor {
         File file
         File sourceFile
         File javadocFile
+        ModuleVersionIdentifier id
     }
 
     static class UnresolvedIdeRepoFileDependency extends IdeRepoFileDependency {
@@ -85,32 +86,35 @@ class IdeDependenciesExtractor {
 
         Map<String, File> javadocFiles = downloadJavadoc ? getFiles(confContainer.detachedConfiguration(javadocDependencies as Dependency[]), "javadoc") : [:]
 
-        resolvedExternalDependencies(plusConfigurations, minusConfigurations).each { File binaryFile, Configuration conf ->
-            File sourceFile = sourceFiles[binaryFile.name]
-            File javadocFile = javadocFiles[binaryFile.name]
-            out << new IdeRepoFileDependency( file: binaryFile, sourceFile: sourceFile, javadocFile: javadocFile, declaredConfiguration: conf)
+        resolvedExternalDependencies(plusConfigurations, minusConfigurations).each { IdeRepoFileDependency dependency ->
+            dependency.sourceFile = sourceFiles[dependency.file.name]
+            dependency.javadocFile = javadocFiles[dependency.file.name]
+            out << dependency
         }
 
-        unresolvedExternalDependencies(plusConfigurations, minusConfigurations) { config, dep ->
-            out << new UnresolvedIdeRepoFileDependency(problem: dep.problem, file: new File("unresolved dependency - $dep.id"), declaredConfiguration: config)
-        }
+        out.addAll(unresolvedExternalDependencies(plusConfigurations, minusConfigurations))
 
         out
     }
 
-    private void unresolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations, Closure action) {
-        def unresolved = new LinkedHashMap<String, Map>()
+    private Collection<UnresolvedIdeRepoFileDependency> unresolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        def unresolved = new LinkedHashMap<String, UnresolvedIdeRepoFileDependency>()
         for (c in plusConfigurations) {
             def deps = c.resolvedConfiguration.lenientConfiguration.unresolvedModuleDependencies
-            deps.each { unresolved[it.id] = [dep: it, config: c] }
+            deps.each {
+                unresolved[it.selector] = new UnresolvedIdeRepoFileDependency(
+                    file: new File(unresolvedFileName(it)), declaredConfiguration: c)
+            }
         }
         for (c in minusConfigurations) {
             def deps = c.resolvedConfiguration.lenientConfiguration.unresolvedModuleDependencies
-            deps.each { unresolved.remove(it.id) }
-        }
-        unresolved.values().each {
-            action.call(it.config, it.dep)
+            deps.each { unresolved.remove(it.selector) }
         }
+        unresolved.values()
+    }
+
+    private String unresolvedFileName(UnresolvedDependency dep) {
+        "unresolved dependency - $dep.selector.group $dep.selector.name $dep.selector.version"
     }
 
     List<IdeLocalFileDependency> extractLocalFileDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
@@ -132,19 +136,19 @@ class IdeDependenciesExtractor {
         }
     }
 
-    private Map<File, Configuration> resolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        LinkedHashMap<File, Configuration> fileToConf = [:]
+    protected Collection<IdeRepoFileDependency> resolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        LinkedHashMap<File, IdeRepoFileDependency> out = [:]
         for (plusConfiguration in plusConfigurations) {
-            for (file in plusConfiguration.resolvedConfiguration.lenientConfiguration.getFiles( { it instanceof ExternalDependency } as Spec)) {
-                fileToConf[file] = plusConfiguration
+            for (artifact in plusConfiguration.resolvedConfiguration.lenientConfiguration.getArtifacts({ it instanceof ExternalDependency } as Spec)) {
+                out[artifact.file] = new IdeRepoFileDependency( file: artifact.file, declaredConfiguration: plusConfiguration, id: artifact.moduleVersion.id)
             }
         }
         for (minusConfiguration in minusConfigurations) {
-            for (file in minusConfiguration.resolvedConfiguration.lenientConfiguration.getFiles({ it instanceof ExternalDependency } as Spec)) {
-                fileToConf.remove(file)
+            for (artifact in minusConfiguration.resolvedConfiguration.lenientConfiguration.getArtifacts({ it instanceof ExternalDependency } as Spec)) {
+                out.remove(artifact.file)
             }
         }
-        fileToConf
+        out.values()
     }
 
     private Set<ResolvedDependency> resolveDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
index 09cf93d..4ac8e9e 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
@@ -30,7 +30,8 @@ public class BuildModelAction implements GradleLauncherAction<ProjectVersion3> {
     public BuildModelAction(Class<? extends ProjectVersion3> type) {
         List<? extends BuildsModel> modelBuilders = asList(
                 new EclipseModelBuilder(), new IdeaModelBuilder(),
-                new GradleProjectBuilder(), new BasicIdeaModelBuilder());
+                new GradleProjectBuilder(), new BasicIdeaModelBuilder(),
+                new MigrationModelBuilder());
 
         for (BuildsModel builder : modelBuilders) {
             if (builder.canBuild(type)) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java
index e60dbc4..846cb92 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/EclipseModelBuilder.java
@@ -97,12 +97,13 @@ public class EclipseModelBuilder implements BuildsModel {
         final List<EclipseSourceDirectoryVersion1> sourceDirectories = new LinkedList<EclipseSourceDirectoryVersion1>();
 
         for (ClasspathEntry entry : entries) {
+            //TODO SF find out why we leave out Variables here.
             if (entry instanceof Library) {
                 AbstractLibrary library = (AbstractLibrary) entry;
                 final File file = library.getLibrary().getFile();
                 final File source = library.getSourcePath() == null ? null : library.getSourcePath().getFile();
                 final File javadoc = library.getJavadocPath() == null ? null : library.getJavadocPath().getFile();
-                externalDependencies.add(new DefaultEclipseExternalDependency(file, javadoc, source));
+                externalDependencies.add(new DefaultEclipseExternalDependency(file, javadoc, source, library.getModuleVersion()));
             } else if (entry instanceof ProjectDependency) {
                 final ProjectDependency projectDependency = (ProjectDependency) entry;
                 final String path = StringUtils.removeStart(projectDependency.getPath(), "/");
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
index 7035886..f8ea32d 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
@@ -20,6 +20,7 @@ import org.gradle.api.Project;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.plugins.ide.idea.IdeaPlugin;
 import org.gradle.plugins.ide.idea.model.*;
+import org.gradle.tooling.internal.gradle.DefaultGradleModuleVersion;
 import org.gradle.tooling.internal.idea.*;
 import org.gradle.tooling.internal.protocol.InternalIdeaProject;
 import org.gradle.tooling.internal.protocol.ProjectVersion3;
@@ -84,12 +85,16 @@ public class IdeaModelBuilder implements BuildsModel {
         for (Dependency dependency : resolved) {
             if (dependency instanceof SingleEntryModuleLibrary) {
                 SingleEntryModuleLibrary d = (SingleEntryModuleLibrary) dependency;
-                IdeaDependency defaultDependency = new DefaultIdeaSingleEntryLibraryDependency()
+                DefaultIdeaSingleEntryLibraryDependency defaultDependency = new DefaultIdeaSingleEntryLibraryDependency()
                         .setFile(d.getLibraryFile())
                         .setSource(d.getSourceFile())
                         .setJavadoc(d.getJavadocFile())
                         .setScope(new DefaultIdeaDependencyScope(d.getScope()))
                         .setExported(d.getExported());
+
+                if (d.getModuleVersion() != null) {
+                    defaultDependency.setExternalGradleModule(new DefaultGradleModuleVersion(d.getModuleVersion()));
+                }
                 dependencies.add(defaultDependency);
             } else if (dependency instanceof ModuleDependency) {
                 ModuleDependency d = (ModuleDependency) dependency;
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/MigrationModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/MigrationModelBuilder.java
new file mode 100644
index 0000000..4d99897
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/MigrationModelBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.internal.provider;
+
+import com.google.common.collect.Sets;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.tasks.testing.Test;
+import org.gradle.tooling.internal.migration.DefaultArchive;
+import org.gradle.tooling.internal.migration.DefaultProjectOutput;
+import org.gradle.tooling.internal.migration.DefaultTestResult;
+import org.gradle.tooling.internal.protocol.InternalProjectOutput;
+import org.gradle.tooling.internal.protocol.ProjectVersion3;
+import org.gradle.tooling.model.internal.migration.ProjectOutput;
+import org.gradle.tooling.model.internal.migration.TaskOutput;
+
+import java.util.Set;
+
+public class MigrationModelBuilder implements BuildsModel {
+    public boolean canBuild(Class<?> type) {
+        return type == InternalProjectOutput.class;
+    }
+
+    public ProjectVersion3 buildAll(GradleInternal gradle) {
+        return buildProjectOutput(gradle.getRootProject(), null);
+
+    }
+
+    private DefaultProjectOutput buildProjectOutput(Project project, ProjectOutput parent) {
+        Set<TaskOutput> taskOutputs = Sets.newHashSet();
+        addArchives(project, taskOutputs);
+        addTestResults(project, taskOutputs);
+
+        DefaultProjectOutput projectOutput = new DefaultProjectOutput(project.getName(),
+                project.getPath(), project.getDescription(), project.getProjectDir(), taskOutputs, parent);
+        for (Project child : project.getChildProjects().values()) {
+            projectOutput.addChild(buildProjectOutput(child, projectOutput));
+        }
+
+        return projectOutput;
+    }
+
+    private void addArchives(Project project, Set<TaskOutput> taskOutputs) {
+        Configuration configuration = project.getConfigurations().findByName("archives");
+        if (configuration != null) {
+            for (PublishArtifact artifact : configuration.getArtifacts()) {
+                taskOutputs.add(new DefaultArchive(artifact.getFile()));
+            }
+        }
+    }
+
+    private void addTestResults(Project project, Set<TaskOutput> taskOutputs) {
+        Set<Test> testTasks = project.getTasks().withType(Test.class);
+        for (Test task : testTasks) {
+            taskOutputs.add(new DefaultTestResult(task.getTestResultsDir()));
+        }
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
index 0365780..dfd764f 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/IdeaPluginTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.plugins.ide.idea
 
+import org.gradle.api.JavaVersion
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.internal.project.DefaultProject
@@ -40,7 +41,7 @@ class IdeaPluginTest extends Specification {
         ideaProjectTask instanceof GenerateIdeaProject
         ideaProjectTask.outputFile == new File(project.projectDir, project.name + ".ipr")
         ideaProjectTask.ideaProject.modules == [project.idea.module, childProject.idea.module]
-        ideaProjectTask.ideaProject.jdkName == "1.6"
+        ideaProjectTask.ideaProject.jdkName == JavaVersion.current().toString()
         ideaProjectTask.ideaProject.languageLevel.level == "JDK_1_6"
 
         childProject.tasks.findByName('ideaProject') == null
@@ -84,7 +85,6 @@ class IdeaPluginTest extends Specification {
         project.apply(plugin: 'java')
 
         then:
-        project.idea.project.jdkName == project.sourceCompatibility.toString()
         project.idea.project.languageLevel.level == new IdeaLanguageLevel(project.sourceCompatibility).level
 
         def configurations = project.configurations
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractorTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractorTest.groovy
deleted file mode 100644
index 681a848..0000000
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractorTest.groovy
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.plugins.ide.internal
-
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.artifacts.LenientConfiguration
-import org.gradle.api.artifacts.ResolvedConfiguration
-import spock.lang.Specification
-
-class IdeDependenciesExtractorTest extends Specification {
-    final IdeDependenciesExtractor extractor = new IdeDependenciesExtractor()
-    final Configuration configuration = Mock()
-    final ResolvedConfiguration resolvedConfiguration = Mock()
-    final LenientConfiguration lenientConfiguration = Mock()
-
-    def "returns dependency entries in the order they were resolved in"() {
-        given:
-        def actualDependencies = [module('a'), module('b'), module('c'), module('d'), module('z')] as LinkedHashSet
-        configuration.resolvedConfiguration >> resolvedConfiguration
-        resolvedConfiguration.lenientConfiguration >> lenientConfiguration
-        lenientConfiguration.getFiles(_) >> actualDependencies
-        lenientConfiguration.unresolvedModuleDependencies >> []
-
-        when:
-        def dependencies = extractor.extractRepoFileDependencies(Mock(ConfigurationContainer), [configuration], [], false, false)
-
-        then:
-        assert dependencies.collect {it.file.name} == ['a', 'b', 'c', 'd', 'z']
-    }
-
-    def module(String name) {
-        return new File(name);
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy
deleted file mode 100644
index 49cc345..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy
+++ /dev/null
@@ -1,799 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.integtests
-
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.util.TestFile
-import org.gradle.util.TestPrecondition
-import org.junit.Rule
-import org.junit.Test
-import static org.hamcrest.Matchers.equalTo
-import static org.junit.Assert.assertThat
-
-public class ArchiveIntegrationTest extends AbstractIntegrationTest {
-    @Test public void canCopyFromAZip() {
-        createZip('test.zip') {
-            subdir1 {
-                file 'file1.txt'
-            }
-            subdir2 {
-                file 'file2.txt'
-                file 'file2.xml'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task copy(type: Copy) {
-                from zipTree('test.zip')
-                exclude '**/*.xml'
-                into 'dest'
-            }
-'''
-
-        inTestDirectory().withTasks('copy').run()
-
-        testFile('dest').assertHasDescendants('subdir1/file1.txt', 'subdir2/file2.txt')
-    }
-
-    @Test public void canCopyFromATar() {
-        createTar('test.tar') {
-            subdir1 {
-                file 'file1.txt'
-            }
-            subdir2 {
-                file 'file2.txt'
-                file 'file2.xml'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task copy(type: Copy) {
-                from tarTree('test.tar')
-                exclude '**/*.xml'
-                into 'dest'
-            }
-'''
-
-        inTestDirectory().withTasks('copy').run()
-
-        testFile('dest').assertHasDescendants('subdir1/file1.txt', 'subdir2/file2.txt')
-    }
-
-    @Test public void "handles gzip compressed tars"() {
-        TestFile tar = file()
-        tar.create {
-            someDir {
-                file '1.txt'
-                file '2.txt'
-            }
-        }
-        tar.tgzTo(file('test.tgz'))
-
-        file('build.gradle') << '''
-            task copy(type: Copy) {
-                from tarTree('test.tgz')
-                exclude '**/2.txt'
-                into 'dest'
-            }
-'''
-
-        inTestDirectory().withTasks('copy').run()
-
-        file('dest').assertHasDescendants('someDir/1.txt')
-    }
-
-    @Test public void "allows user to provide a custom resource for the tarTree"() {
-        TestFile tar = file()
-        tar.create {
-            someDir {
-                file '1.txt'
-            }
-        }
-        tar.tarTo(file('test.tar'))
-
-        file('build.gradle') << '''
-            def res = new ReadableResource() {
-                InputStream read() { new FileInputStream(file('test.tar')) }
-                String getBaseName() { "foo" }
-                URI getURI() { new java.net.URI("foo") }
-                String getDisplayName() { "The foo" }
-            }
-
-            task copy(type: Copy) {
-                from tarTree(res)
-                into 'dest'
-            }
-'''
-
-        inTestDirectory().withTasks('copy').run()
-
-        file('dest').assertHasDescendants('someDir/1.txt')
-    }
-
-    @Test public void "handles bzip2 compressed tars"() {
-        TestFile tar = file()
-        tar.create {
-            someDir {
-                file '1.txt'
-                file '2.txt'
-            }
-        }
-        tar.tbzTo(file('test.tbz2'))
-
-        file('build.gradle') << '''
-            task copy(type: Copy) {
-                from tarTree('test.tbz2')
-                exclude '**/2.txt'
-                into 'dest'
-            }
-'''
-
-        inTestDirectory().withTasks('copy').run()
-
-        file('dest').assertHasDescendants('someDir/1.txt')
-    }
-
-     @Test public void "knows compression of the tar"() {
-        TestFile tar = file()
-        tar.tbzTo(file('test.tbz2'))
-
-        file('build.gradle') << '''
-            task myTar(type: Tar) {
-                assert compression == Compression.NONE
-
-                compression = Compression.GZIP
-                assert compression == Compression.GZIP
-
-                compression = Compression.BZIP2
-                assert compression == Compression.BZIP2
-            }
-'''
-
-        inTestDirectory().withTasks('myTar').run()
-    }
-
-    @Test public void "can choose compression method for tarTree"() {
-        TestFile tar = file()
-        tar.create {
-            someDir {
-                file '1.txt'
-                file '2.txt'
-            }
-        }
-        //file extension is non-standard:
-        tar.tbzTo(file('test.ext'))
-
-        file('build.gradle') << '''
-            task copy(type: Copy) {
-                from tarTree(resources.bzip2('test.ext'))
-                exclude '**/2.txt'
-                into 'dest'
-            }
-'''
-
-        inTestDirectory().withTasks('copy').run()
-
-        file('dest').assertHasDescendants('someDir/1.txt')
-    }
-
-    @Rule public final TestResources resources = new TestResources()
-
-    @Test public void "tarTreeFailsGracefully"() {
-        file('build.gradle') << '''
-            task copy(type: Copy) {
-                //the input file comes from the resources to make sure it is truly improper 'tar', see GRADLE-1952
-                from tarTree('compressedTarWithWrongExtension.tar')
-                into 'dest'
-            }
-'''
-
-        def failure = inTestDirectory().withTasks('copy').runWithFailure()
-
-        assert failure.error.contains("Unable to expand TAR")
-        assert failure.error.contains("compression based on the file extension")
-    }
-
-    @Test public void cannotCreateAnEmptyZip() {
-        testFile('build.gradle') << '''
-            task zip(type: Zip) {
-                from 'test'
-                destinationDir = buildDir
-                archiveName = 'test.zip'
-}
-'''
-
-        inTestDirectory().withTasks('zip').run()
-
-        testFile('build/test.zip').assertDoesNotExist()
-    }
-
-    @Test public void canCreateAnEmptyJar() {
-        testFile('build.gradle') << '''
-            task jar(type: Jar) {
-                from 'test'
-                destinationDir = buildDir
-                archiveName = 'test.jar'
-}
-'''
-
-        inTestDirectory().withTasks('jar').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.jar').unzipTo(expandDir)
-        expandDir.assertHasDescendants('META-INF/MANIFEST.MF')
-
-        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
-    }
-
-    @Test public void cannotCreateAnEmptyTar() {
-        testFile('build.gradle') << '''
-            task tar(type: Tar) {
-                from 'test'
-                destinationDir = buildDir
-                archiveName = 'test.tar'
-}
-'''
-
-        inTestDirectory().withTasks('tar').run()
-
-        testFile('build/test.tar').assertDoesNotExist()
-    }
-
-    @Test public void canCreateAZipArchive() {
-        createDir('test') {
-            dir1 {
-                file('file1.txt').write("abc")
-            }
-            file 'file1.txt'
-            dir2 {
-                file 'file2.txt'
-                file 'script.sh'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task zip(type: Zip) {
-                into('prefix') {
-                    from 'test'
-                    include '**/*.txt'
-                    rename { "renamed_$it" }
-                    filter { "[$it]" }
-                }
-                into('scripts') {
-                    from 'test'
-                    include '**/*.sh'
-                    dirMode = 0750
-                    fileMode = 0754
-                }
-                destinationDir = buildDir
-                archiveName = 'test.zip'
-            }
-'''
-
-        inTestDirectory().withTasks('zip').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.zip').usingNativeTools().unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'prefix/dir1/renamed_file1.txt',
-                'prefix/renamed_file1.txt',
-                'prefix/dir2/renamed_file2.txt',
-                'scripts/dir2/script.sh')
-
-        expandDir.file('prefix/dir1/renamed_file1.txt').assertContents(equalTo('[abc]'))
-
-        if (TestPrecondition.FILE_PERMISSIONS.fulfilled) {
-            expandDir.file('scripts/dir2').assertPermissions(equalTo("rwxr-x---"))
-            expandDir.file('scripts/dir2/script.sh').assertPermissions(equalTo("rwxr-xr--"))
-        }
-    }
-
-    @Test public void canCreateATarArchive() {
-        createDir('test') {
-            dir1 {
-                file('file1.txt').write 'abc'
-            }
-            file 'file1.txt'
-            dir2 {
-                file 'file2.txt'
-                file 'script.sh'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task tar(type: Tar) {
-                from('test') {
-                    include '**/*.txt'
-                    filter { "[$it]" }
-                }
-                from('test') {
-                    include '**/*.sh'
-                    into 'scripts'
-                    fileMode = 0754
-                    dirMode = 0750
-                }
-                destinationDir = buildDir
-                archiveName = 'test.tar'
-            }
-'''
-
-        inTestDirectory().withTasks('tar').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.tar').usingNativeTools().untarTo(expandDir)
-        expandDir.assertHasDescendants('dir1/file1.txt', 'file1.txt', 'dir2/file2.txt', 'scripts/dir2/script.sh')
-
-        expandDir.file('dir1/file1.txt').assertContents(equalTo('[abc]'))
-
-        if (TestPrecondition.FILE_PERMISSIONS.fulfilled) {
-            expandDir.file('scripts/dir2').assertPermissions(equalTo("rwxr-x---"))
-            expandDir.file('scripts/dir2/script.sh').assertPermissions(equalTo("rwxr-xr--"))
-        }
-    }
-
-    @Test public void canCreateATgzArchive() {
-        createDir('test') {
-            dir1 {
-                file 'file1.txt'
-            }
-            file 'file1.txt'
-            dir2 {
-                file 'file2.txt'
-                file 'ignored.xml'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task tar(type: Tar) {
-                compression = Compression.GZIP
-                from 'test'
-                include '**/*.txt'
-                destinationDir = buildDir
-                archiveName = 'test.tgz'
-            }
-'''
-
-        inTestDirectory().withTasks('tar').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.tgz').untarTo(expandDir)
-        expandDir.assertHasDescendants('dir1/file1.txt', 'file1.txt', 'dir2/file2.txt')
-    }
-
-    @Test public void canCreateATbzArchive() {
-        createDir('test') {
-            dir1 {
-                file 'file1.txt'
-            }
-            file 'file1.txt'
-            dir2 {
-                file 'file2.txt'
-                file 'ignored.xml'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task tar(type: Tar) {
-                compression = Compression.BZIP2
-                from 'test'
-                include '**/*.txt'
-                destinationDir = buildDir
-                archiveName = 'test.tbz2'
-            }
-'''
-
-        inTestDirectory().withTasks('tar').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.tbz2').untarTo(expandDir)
-        expandDir.assertHasDescendants('dir1/file1.txt', 'file1.txt', 'dir2/file2.txt')
-    }
-
-    @Test public void canCreateAJarArchiveWithDefaultManifest() {
-        createDir('test') {
-            dir1 {
-                file 'file1.txt'
-            }
-        }
-        createDir('meta-inf') {
-            file 'file1.txt'
-            dir2 {
-                file 'file2.txt'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task jar(type: Jar) {
-                from 'test'
-                metaInf {
-                    from 'meta-inf'
-                }
-                destinationDir = buildDir
-                archiveName = 'test.jar'
-            }
-'''
-
-        inTestDirectory().withTasks('jar').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.jar').unzipTo(expandDir)
-        expandDir.assertHasDescendants('META-INF/MANIFEST.MF', 'META-INF/file1.txt', 'META-INF/dir2/file2.txt', 'dir1/file1.txt')
-
-        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
-    }
-
-    @Test public void metaInfSpecsAreIndependentOfOtherSpec() {
-        createDir('test') {
-            dir1 {
-                file 'ignored.xml'
-                file 'file1.txt'
-            }
-        }
-        createDir('meta-inf') {
-            dir2 {
-                file 'ignored.txt'
-                file 'file2.xml'
-            }
-        }
-        createDir('meta-inf2') {
-            file 'file2.txt'
-            file 'file2.xml'
-        }
-
-        testFile('build.gradle') << '''
-            task jar(type: Jar) {
-                from 'test'
-                include '**/*.txt'
-                metaInf {
-                    from 'meta-inf'
-                    include '**/*.xml'
-                }
-                metaInf {
-                    from 'meta-inf2'
-                    into 'dir3'
-                }
-                destinationDir = buildDir
-                archiveName = 'test.jar'
-            }
-'''
-
-        inTestDirectory().withTasks('jar').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.jar').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'META-INF/dir2/file2.xml',
-                'META-INF/dir3/file2.txt',
-                'META-INF/dir3/file2.xml',
-                'dir1/file1.txt')
-    }
-
-    @Test public void canCreateAWarArchiveWithNoWebXml() {
-        createDir('content') {
-            content1 {
-                file 'file1.jsp'
-            }
-        }
-        createDir('web-inf') {
-            webinf1 {
-                file 'file1.txt'
-            }
-        }
-        createDir('meta-inf') {
-            metainf1 {
-                file 'file2.txt'
-            }
-        }
-        createDir('classes') {
-            org {
-                gradle {
-                    file 'resource.txt'
-                    file 'Person.class'
-                }
-            }
-        }
-        createZip("lib.jar") {
-            file "Dependency.class"
-        }
-
-        testFile('build.gradle') << '''
-            task war(type: War) {
-                from 'content'
-                metaInf {
-                    from 'meta-inf'
-                }
-                webInf {
-                    from 'web-inf'
-                }
-                classpath 'classes'
-                classpath 'lib.jar'
-                destinationDir = buildDir
-                archiveName = 'test.war'
-            }
-'''
-
-        inTestDirectory().withTasks('war').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.war').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'META-INF/metainf1/file2.txt',
-                'content1/file1.jsp',
-                'WEB-INF/lib/lib.jar',
-                'WEB-INF/classes/org/gradle/resource.txt',
-                'WEB-INF/classes/org/gradle/Person.class',
-                'WEB-INF/webinf1/file1.txt')
-
-        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
-    }
-
-    @Test public void canCreateAWarArchiveWithWebXml() {
-        testFile('some.xml') << '<web/>'
-        createDir('web-inf') {
-            webinf1 {
-                file 'file1.txt'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task war(type: War) {
-                webInf {
-                    from 'web-inf'
-                    exclude '**/*.xml'
-                }
-                webXml = file('some.xml')
-                destinationDir = buildDir
-                archiveName = 'test.war'
-            }
-'''
-
-        inTestDirectory().withTasks('war').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.war').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'WEB-INF/web.xml',
-                'WEB-INF/webinf1/file1.txt')
-    }
-
-    @Test public void canAddFilesToWebInfDir() {
-        createDir('web-inf') {
-            webinf1 {
-                file 'file1.txt'
-                file 'ignore.xml'
-            }
-        }
-        createDir('web-inf2') {
-            file 'file2.txt'
-        }
-
-        testFile('build.gradle') << '''
-            task war(type: War) {
-                webInf {
-                    from 'web-inf'
-                    exclude '**/*.xml'
-                }
-                webInf {
-                    from 'web-inf2'
-                    into 'dir2'
-                    include '**/file2*'
-                }
-                destinationDir = buildDir
-                archiveName = 'test.war'
-            }
-'''
-
-        inTestDirectory().withTasks('war').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.war').unzipTo(expandDir)
-        expandDir.assertHasDescendants(
-                'META-INF/MANIFEST.MF',
-                'WEB-INF/webinf1/file1.txt',
-                'WEB-INF/dir2/file2.txt')
-    }
-
-    @Test public void canCreateArchivesAndExplodedImageFromSameSpec() {
-        createDir('test') {
-            dir1 {
-                file 'file1.txt'
-                file 'ignored.xml'
-            }
-            dir2 {
-                dir3 { file 'file2.txt' }
-                file 'ignored.xml'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            def distImage = copySpec {
-                include '**/*.txt'
-                from('test/dir1') {
-                    into 'lib'
-                }
-                from('test/dir2') {
-                    into 'src'
-                }
-            }
-            task copy(type: Copy) {
-                into 'build/exploded'
-                with distImage
-            }
-            task zip(type: Zip) {
-                destinationDir = file('build')
-                archiveName = 'test.zip'
-                into 'prefix'
-                with distImage
-            }
-'''
-
-        inTestDirectory().withTasks('copy', 'zip').run()
-        testFile('build/exploded').assertHasDescendants(
-                'lib/file1.txt', 'src/dir3/file2.txt'
-        )
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.zip').unzipTo(expandDir)
-        expandDir.assertHasDescendants('prefix/lib/file1.txt', 'prefix/src/dir3/file2.txt')
-    }
-
-    @Test public void canCreateExplodedImageFromArchiveTask() {
-        createDir('test') {
-            dir1 {
-                file 'file1.txt'
-                file 'ignored.xml'
-            }
-            dir2 {
-                dir3 { file 'file2.txt' }
-                file 'ignored.xml'
-            }
-        }
-
-        testFile('build.gradle') << '''
-            task zip(type: Zip) {
-                destinationDir = file('build')
-                archiveName = 'test.zip'
-                into 'prefix'
-                from 'test'
-                include '**/*.txt'
-            }
-            task explodedZip(type: Copy) {
-                into 'build/exploded'
-                with zip
-            }
-            task copyFromRootSpec(type: Copy) {
-                into 'build/copy'
-                with zip.rootSpec
-            }
-'''
-
-        inTestDirectory().withTasks('explodedZip', 'copyFromRootSpec').run()
-        testFile('build/exploded').assertHasDescendants(
-                'prefix/dir1/file1.txt', 'prefix/dir2/dir3/file2.txt'
-        )
-        testFile('build/copy').assertHasDescendants(
-                'prefix/dir1/file1.txt', 'prefix/dir2/dir3/file2.txt'
-        )
-    }
-
-    @Test public void canMergeArchivesIntoAnotherZip() {
-        createZip('test.zip') {
-            shared {
-                file 'zip.txt'
-            }
-            zipdir1 {
-                file 'file1.txt'
-            }
-        }
-        createTar('test.tar') {
-            shared {
-                file 'tar.txt'
-            }
-            tardir1 {
-                file 'file1.txt'
-            }
-        }
-        createDir('test') {
-            shared {
-                file 'dir.txt'
-            }
-            dir1 {
-                file 'file1.txt'
-            }
-        }
-
-        testFile('build.gradle') << '''
-        task zip(type: Zip) {
-            from zipTree('test.zip')
-            from tarTree('test.tar')
-            from fileTree('test')
-            destinationDir = buildDir
-            archiveName = 'test.zip'
-        }
-        '''
-
-        inTestDirectory().withTasks('zip').run()
-
-        TestFile expandDir = testFile('expanded')
-        testFile('build/test.zip').unzipTo(expandDir)
-        expandDir.assertHasDescendants('shared/zip.txt', 'zipdir1/file1.txt', 'shared/tar.txt', 'tardir1/file1.txt', 'shared/dir.txt', 'dir1/file1.txt')
-    }
-
-    @Test public void usesManifestFromJarTaskWhenMergingJars() {
-        createDir('src1') {
-            dir1 { file 'file1.txt' }
-        }
-        createDir('src2') {
-            dir2 { file 'file2.txt' }
-        }
-        testFile('build.gradle') << '''
-        task jar1(type: Jar) {
-            from 'src1'
-            destinationDir = buildDir
-            archiveName = 'test1.zip'
-            manifest { attributes(attr: 'jar1') }
-        }
-        task jar2(type: Jar) {
-            from 'src2'
-            destinationDir = buildDir
-            archiveName = 'test2.zip'
-            manifest { attributes(attr: 'jar2') }
-        }
-        task jar(type: Jar) {
-            dependsOn jar1, jar2
-            from zipTree(jar1.archivePath), zipTree(jar2.archivePath)
-            manifest { attributes(attr: 'value') }
-            destinationDir = buildDir
-            archiveName = 'test.zip'
-        }
-        '''
-
-        inTestDirectory().withTasks('jar').run()
-        TestFile jar = testFile('build/test.zip')
-
-        def manifest = jar.manifest
-        println manifest.mainAttributes
-        assertThat(manifest.mainAttributes.getValue('attr'), equalTo('value'))
-
-        TestFile expandDir = testFile('expected')
-        jar.unzipTo(expandDir)
-        expandDir.assertHasDescendants('dir1/file1.txt', 'dir2/file2.txt', 'META-INF/MANIFEST.MF')
-    }
-
-    private def createZip(String name, Closure cl) {
-        TestFile zipRoot = testFile("${name}.root")
-        TestFile zip = testFile(name)
-        zipRoot.create(cl)
-        zipRoot.zipTo(zip)
-    }
-
-    private def createTar(String name, Closure cl) {
-        TestFile tarRoot = testFile("${name}.root")
-        TestFile tar = testFile(name)
-        tarRoot.create(cl)
-        tarRoot.tarTo(tar)
-    }
-
-    private def createDir(String name, Closure cl) {
-        TestFile root = testFile(name)
-        root.create(cl)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy
new file mode 100644
index 0000000..3e912e0
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+import org.gradle.util.TestFile
+
+class BuildSourceBuilderIntegrationTest extends AbstractIntegrationSpec {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2032")
+    def "can simultaneously run gradle on projects with buildSrc"() {
+        given:
+        def buildSrcDir = file("buildSrc").createDir()
+        writeSharedClassFile(buildSrcDir);
+        buildFile.text = """
+        import org.gradle.integtest.test.BuildSrcTask
+
+        task blocking(type:BuildSrcTask)<< {
+            file("run1washere.lock").createNewFile()
+            while(!file("run2washere.lock").exists()){
+                sleep 10
+            }
+        }
+
+        task releasing(type:BuildSrcTask) << {
+            while(!file("run1washere.lock").exists()){
+                sleep 10
+            }
+            file("run2washere.lock").createNewFile()
+        }
+        """
+        when:
+        def handleRun1 = executer.withTasks("blocking").start()
+        def handleRun2 = executer.withTasks("releasing").start()
+        and:
+        def finish2 = handleRun2.waitForFinish()
+        def finish1 = handleRun1.waitForFinish()
+        then:
+        finish1.error.equals("")
+        finish2.error.equals("")
+        finish1.assertTasksExecuted(":blocking")
+        finish2.assertTasksExecuted(":releasing")
+    }
+
+    void writeSharedClassFile(TestFile targetDirectory) {
+        def packageDirectory = targetDirectory.createDir("src/main/java/org/gradle/integtest/test")
+        new File(packageDirectory, "BuildSrcTask.java").text = """
+        package org.gradle.integtest.test;
+        import org.gradle.api.DefaultTask;
+        import org.gradle.api.tasks.TaskAction;
+
+        public class BuildSrcTask extends DefaultTask{
+            @TaskAction public void defaultAction(){
+                System.out.println(String.format("BuildSrcTask '%s' executed.", getName()));
+            }
+        }
+        """
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
index f041714..b8113b9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
@@ -16,26 +16,27 @@
 
 package org.gradle.integtests
 
+import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.groovy.scripts.UriScriptSource
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.MavenRepository
 import org.gradle.util.GradleVersion
 import org.gradle.util.TestFile
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+
 import static org.junit.Assert.assertEquals
-import org.gradle.api.internal.artifacts.ivyservice.DefaultCacheLockingManager
 
 /**
  * @author Hans Dockter
  */
-class CacheProjectIntegrationTest {
+public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
     static final String TEST_FILE = "build/test.txt"
 
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final HttpServer server = new HttpServer()
 
     TestFile projectDir
     TestFile userHomeDir
@@ -44,20 +45,30 @@ class CacheProjectIntegrationTest {
     TestFile classFile
     TestFile artifactsCache
 
+    MavenRepository repo
+
     @Before
     public void setUp() {
         // Use own home dir so we don't blast the shared one when we run with -C rebuild
-        dist.requireOwnUserHomeDir()
+        distribution.requireOwnUserHomeDir()
 
         String version = GradleVersion.current().version
-        projectDir = dist.getTestDir().file("project")
+        projectDir = distribution.getTestDir().file("project")
         projectDir.mkdirs()
-        userHomeDir = dist.getUserHomeDir()
+        userHomeDir = distribution.getUserHomeDir()
         buildFile = projectDir.file('build.gradle')
         ScriptSource source = new UriScriptSource("build file", buildFile)
         propertiesFile = userHomeDir.file("caches/$version/scripts/$source.className/ProjectScript/no_buildscript/cache.properties")
         classFile = userHomeDir.file("caches/$version/scripts/$source.className/ProjectScript/no_buildscript/classes/${source.className}.class")
         artifactsCache = projectDir.file(".gradle/$version/taskArtifacts/taskArtifacts.bin")
+
+        def repoDir = file("repo")
+        repo = maven(repoDir)
+        server.allowGet("/repo", repo.rootDir)
+        repo.module("commons-io", "commons-io", "1.4").publish()
+        repo.module("commons-lang", "commons-lang", "2.6").publish()
+
+        server.start()
     }
 
     @Test
@@ -154,7 +165,11 @@ class CacheProjectIntegrationTest {
     def createLargeBuildScript() {
         File buildFile = projectDir.file('build.gradle')
         String content = """
-repositories { mavenCentral() }
+repositories {
+    maven{
+        url "http://localhost:${server.port}/repo"
+    }
+}
 configurations { compile }
 dependencies { compile 'commons-io:commons-io:1.4 at jar' }
 """
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
index 115bd01..af06ec0 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.integtests
 
-import org.apache.tools.ant.taskdefs.Chmod
 import org.gradle.integtests.fixtures.ExecutionFailure
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.GradleDistributionExecuter
@@ -177,6 +176,9 @@ public class CommandLineIntegrationTest {
 
         def result = executer.usingExecutable(script.absolutePath).withTasks("help").run()
         assert result.output.contains("my app")
+
+        // Don't follow links when cleaning up test files
+        dist.temporaryFolder.dir.usingNativeTools().deleteDir()
     }
 
     @Test
@@ -184,10 +186,7 @@ public class CommandLineIntegrationTest {
         def binDir = dist.gradleHomeDir.file('bin')
         def newScript = binDir.file(OperatingSystem.current().getScriptName('my app'))
         binDir.file(OperatingSystem.current().getScriptName('gradle')).copyTo(newScript)
-        def chmod = new Chmod()
-        chmod.file = newScript
-        chmod.perm = "700"
-        AntUtil.execute(chmod)
+        newScript.permissions = 'rwx------'
 
         def result = executer.usingExecutable(newScript.absolutePath).withTasks("help").run()
         assert result.output.contains("my app")
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
index e9db413..83dbfdc 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
@@ -100,14 +100,11 @@ class DistributionIntegrationTest {
         // Top level files
         contentsDir.file('LICENSE').assertIsFile()
 
-        // Libs
-        assertIsGradleJar(contentsDir.file("lib/gradle-cli-${version}.jar"))
-        assertIsGradleJar(contentsDir.file("lib/gradle-core-${version}.jar"))
-        assertIsGradleJar(contentsDir.file("lib/gradle-ui-${version}.jar"))
-        assertIsGradleJar(contentsDir.file("lib/gradle-launcher-${version}.jar"))
-        assertIsGradleJar(contentsDir.file("lib/gradle-tooling-api-${version}.jar"))
+        // Core libs
+        def coreLibs = contentsDir.file("lib").listFiles().findAll { it.name.startsWith("gradle-") }
+        assert coreLibs.size() == 10
+        coreLibs.each { assertIsGradleJar(it) }
         def wrapperJar = contentsDir.file("lib/gradle-wrapper-${version}.jar")
-        assertIsGradleJar(wrapperJar)
         assert wrapperJar.length() < 20 * 1024; // wrapper needs to be small. Let's check it's smaller than some arbitrary 'small' limit
 
         // Plugins
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
deleted file mode 100644
index e19b256..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.integtests
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import spock.lang.Issue
-
-class DynamicObjectIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-
-    @Test
-    public void canAddDynamicPropertiesToProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("settings.gradle").writelns("include 'child'");
-        testDir.file("build.gradle").writelns(
-                "ext.rootProperty = 'root'",
-                "ext.sharedProperty = 'ignore me'",
-                "ext.property = 'value'",
-                "convention.plugins.test = new ConventionBean()",
-                "task rootTask",
-                "task testTask",
-                "class ConventionBean { def getConventionProperty() { 'convention' } }"
-        );
-        testDir.file("child/build.gradle").writelns(
-                "ext.childProperty = 'child'",
-                "ext.sharedProperty = 'shared'",
-                "task testTask << {",
-                "  new Reporter().checkProperties(project)",
-                "}",
-                "assert 'root' == rootProperty",
-                "assert 'root' == property('rootProperty')",
-                "assert 'root' == properties.rootProperty",
-                "assert 'child' == childProperty",
-                "assert 'child' == property('childProperty')",
-                "assert 'child' == properties.childProperty",
-                "assert 'shared' == sharedProperty",
-                "assert 'shared' == property('sharedProperty')",
-                "assert 'shared' == properties.sharedProperty",
-                "assert 'convention' == conventionProperty",
-                // Use a separate class, to isolate Project from the script
-                "class Reporter {",
-                "  def checkProperties(object) {",
-                "    assert 'root' == object.rootProperty",
-                "    assert 'child' == object.childProperty",
-                "    assert 'shared' == object.sharedProperty",
-                "    assert 'convention' == object.conventionProperty",
-                "    assert 'value' == object.property",
-                "    assert ':child:testTask' == object.testTask.path",
-                "    try { object.rootTask; fail() } catch (MissingPropertyException e) { }",
-                "  }",
-                "}"
-        );
-
-        executer.inDirectory(testDir).withTasks("testTask").run();
-    }
-
-    @Test
-    public void canAddDynamicMethodsToProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("settings.gradle").writelns("include 'child'");
-        testDir.file("build.gradle").writelns(
-                "def rootMethod(p) { 'root' + p }",
-                "def sharedMethod(p) { 'ignore me' }",
-                "convention.plugins.test = new ConventionBean()",
-                "task rootTask",
-                "task testTask",
-                "class ConventionBean { def conventionMethod(name) { 'convention' + name } }"
-        );
-        testDir.file("child/build.gradle").writelns(
-                "def childMethod(p) { 'child' + p }",
-                "def sharedMethod(p) { 'shared' + p }",
-                "task testTask << {",
-                "  new Reporter().checkMethods(project)",
-                "}",
-                // Use a separate class, to isolate Project from the script
-                "class Reporter {",
-                "  def checkMethods(object) {",
-                "    assert 'rootMethod' == object.rootMethod('Method')",
-                "    assert 'childMethod' == object.childMethod('Method')",
-                "    assert 'sharedMethod'== object.sharedMethod('Method')",
-                "    assert 'conventionMethod' == object.conventionMethod('Method')",
-                "    object.testTask { assert ':child:testTask' == delegate.path }",
-                "    try { object.rootTask { }; fail() } catch (MissingMethodException e) { }",
-                "  }",
-                "}"
-        );
-
-        executer.inDirectory(testDir).withTasks("testTask").run();
-    }
-
-    @Test
-    public void canAddMixinsToProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
-convention.plugins.test = new ConventionBean()
-
-assert conventionProperty == 'convention'
-assert conventionMethod('value') == '[value]'
-
-class ConventionBean {
-    def getConventionProperty() { 'convention' }
-    def conventionMethod(String value) { "[$value]" }
-}
-'''
-
-        executer.inDirectory(testDir).run();
-    }
-
-    @Test
-    public void canAddExtensionsToProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
-extensions.test = new ExtensionBean()
-
-assert test instanceof ExtensionBean
-test { it ->
-    assert it == project.test
-}
-class ExtensionBean {
-}
-'''
-
-        executer.inDirectory(testDir).run();
-    }
-
-    @Test
-    public void canAddPropertiesToProjectUsingGradlePropertiesFile() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("settings.gradle").writelns("include 'child'");
-        testDir.file("gradle.properties") << '''
-global=some value
-'''
-        testDir.file("build.gradle") << '''
-assert 'some value' == global
-assert hasProperty('global')
-assert 'some value' == property('global')
-assert 'some value' == properties.global
-assert 'some value' == project.global
-assert project.hasProperty('global')
-assert 'some value' == project.property('global')
-assert 'some value' == project.properties.global
-'''
-        testDir.file("child/gradle.properties") << '''
-global=overridden value
-'''
-        testDir.file("child/build.gradle") << '''
-assert 'overridden value' == global
-'''
-
-        executer.inDirectory(testDir).run();
-    }
-
-    @Test
-    public void canAddDynamicPropertiesToCoreDomainObjects() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
-            class GroovyTask extends DefaultTask { }
-
-            task defaultTask {
-                ext.custom = 'value'
-            }
-            task javaTask(type: Copy) {
-                ext.custom = 'value'
-            }
-            task groovyTask(type: GroovyTask) {
-                ext.custom = 'value'
-            }
-            configurations {
-                test {
-                    ext.custom = 'value'
-                }
-            }
-            dependencies {
-                test('::name:') {
-                    ext.custom = 'value';
-                }
-                test(module('::other')) {
-                    ext.custom = 'value';
-                }
-                test(project(':')) {
-                    ext.custom = 'value';
-                }
-                test(files('src')) {
-                    ext.custom = 'value';
-                }
-            }
-            repositories {
-                ext.custom = 'repository'
-            }
-            defaultTask.custom = 'another value'
-            javaTask.custom = 'another value'
-            groovyTask.custom = 'another value'
-            assert !project.hasProperty('custom')
-            assert defaultTask.hasProperty('custom')
-            assert defaultTask.custom == 'another value'
-            assert javaTask.custom == 'another value'
-            assert groovyTask.custom == 'another value'
-            assert configurations.test.hasProperty('custom')
-            assert configurations.test.custom == 'value'
-            configurations.test.dependencies.each {
-                assert it.hasProperty('custom')
-                assert it.custom == 'value'
-                assert it.getProperty('custom') == 'value'
-            }
-            assert repositories.hasProperty('custom')
-            assert repositories.custom == 'repository'
-            repositories {
-                assert custom == 'repository'
-            }
-'''
-
-        executer.inDirectory(testDir).withTasks("defaultTask").run();
-    }
-
-    @Test
-    public void canAddMixInsToCoreDomainObjects() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
-            class Extension { def doStuff() { 'method' } }
-            class GroovyTask extends DefaultTask { }
-
-            task defaultTask {
-                convention.plugins.custom = new Extension()
-            }
-            task javaTask(type: Copy) {
-                convention.plugins.custom = new Extension()
-            }
-            task groovyTask(type: GroovyTask) {
-                convention.plugins.custom = new Extension()
-            }
-            configurations {
-                test {
-                    convention.plugins.custom = new Extension()
-                }
-            }
-            dependencies {
-                test('::name:') {
-                    convention.plugins.custom = new Extension()
-                }
-                test(module('::other')) {
-                    convention.plugins.custom = new Extension()
-                }
-                test(project(':')) {
-                    convention.plugins.custom = new Extension()
-                }
-                test(files('src')) {
-                    convention.plugins.custom = new Extension()
-                }
-            }
-            repositories {
-                convention.plugins.custom = new Extension()
-            }
-            assert defaultTask.doStuff() == 'method'
-            assert javaTask.doStuff() == 'method'
-            assert groovyTask.doStuff() == 'method'
-            assert configurations.test.doStuff() == 'method'
-            configurations.test.dependencies.each {
-                assert it.doStuff() == 'method'
-            }
-            assert repositories.doStuff() == 'method'
-            repositories {
-                assert doStuff() == 'method'
-            }
-'''
-
-        executer.inDirectory(testDir).withTasks("defaultTask").run();
-    }
-
-    @Test
-    public void canAddExtensionsToCoreDomainObjects() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
-            class Extension { def doStuff() { 'method' } }
-            class GroovyTask extends DefaultTask { }
-
-            task defaultTask {
-                extensions.test = new Extension()
-            }
-            task javaTask(type: Copy) {
-                extensions.test = new Extension()
-            }
-            task groovyTask(type: GroovyTask) {
-                extensions.test = new Extension()
-            }
-            configurations {
-                test {
-                    extensions.test = new Extension()
-                }
-            }
-            dependencies {
-                test('::name:') {
-                    extensions.test = new Extension()
-                }
-                test(module('::other')) {
-                    extensions.test = new Extension()
-                }
-                test(project(':')) {
-                    extensions.test = new Extension()
-                }
-                test(files('src')) {
-                    extensions.test = new Extension()
-                }
-            }
-            repositories {
-                extensions.test = new Extension()
-            }
-            assert defaultTask.test instanceof Extension
-            assert javaTask.test instanceof Extension
-            assert groovyTask.test instanceof Extension
-            assert configurations.test.test instanceof Extension
-            configurations.test.dependencies.each {
-                assert it.test instanceof Extension
-            }
-            assert repositories.test instanceof Extension
-            repositories {
-                assert test instanceof Extension
-            }
-'''
-
-        executer.inDirectory(testDir).withTasks("defaultTask").run();
-    }
-
-    @Test
-    public void mixesDslMethodsIntoCoreDomainObjects() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
-            class GroovyTask extends DefaultTask {
-                def String prop
-                void doStuff(Action<Task> action) { action.execute(this) }
-            }
-            tasks.withType(GroovyTask) { conventionMapping.prop = { '[default]' } }
-            task test(type: GroovyTask)
-            assert test.prop == '[default]'
-            test {
-                description 'does something'
-                prop 'value'
-            }
-            assert test.description == 'does something'
-            assert test.prop == 'value'
-            test.doStuff {
-                prop = 'new value'
-            }
-            assert test.prop == 'new value'
-'''
-
-        executer.inDirectory(testDir).withTasks("test").run();
-    }
-
-    @Test
-    void canAddExtensionsToDynamicExtensions() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file('build.gradle') << '''
-            class Extension {
-                String name
-                Extension(String name) {
-                    this.name = name
-                }
-            }
-
-            project.extensions.create("l1", Extension, "l1")
-            project.l1.extensions.create("l2", Extension, "l2")
-            project.l1.l2.extensions.create("l3", Extension, "l3")
-
-            task test << {
-                assert project.l1.name == "l1"
-                assert project.l1.l2.name == "l2"
-                assert project.l1.l2.l3.name == "l3"
-            }
-        '''
-
-        executer.inDirectory(testDir).withTasks("test").run();
-    }
-
-    @Test
-    public void canInjectMethodsFromParentProject() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("settings.gradle").writelns("include 'child'");
-        testDir.file("build.gradle").writelns(
-                "subprojects {",
-                "  ext.injectedMethod = { project.name }",
-                "}"
-        );
-        testDir.file("child/build.gradle").writelns(
-                "task testTask << {",
-                "   assert injectedMethod() == 'child'",
-                "}"
-        );
-
-        executer.inDirectory(testDir).withTasks("testTask").run();
-    }
-    
-    @Test void canAddNewPropertiesViaTheAdhocNamespace() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("build.gradle") << """
-            ext {
-                add "p1", 1
-            }
-            assert p1 == 1
-            p2 = 2
-            assert ext.p2 = 2
-            
-            task run << {
-                ext {
-                    add "p1", 1
-                }
-                assert p1 == 1
-                p2 = 2
-                assert ext.p2 = 2            
-            }        
-        """
-        
-        executer.withTasks("run")
-    }
-
-    @Issue("GRADLE-2163")
-    @Test void canDecorateBooleanPrimitiveProperties() {
-        TestFile testDir = dist.getTestDir();
-        testDir.file("build.gradle") << """
-            class CustomBean {
-                boolean b
-            }
-
-            // best way to decorate right now
-            extensions.create('bean', CustomBean)
-
-            task run << {
-                assert bean.b == false
-                bean.b.conventionMapping.map('b') { true }
-                assert bean.b == true
-            }
-        """
-
-        executer.withTasks("run")
-
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.groovy
index 6b6fdd2..6e0e76f 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 the original author or authors.
+ * Copyright 2009 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.
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
index 1c984ad..73e0d4c 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
@@ -59,6 +59,7 @@ class IncrementalJavaProjectBuildIntegrationTest {
 
         jar.assertHasNotChangedSince(snapshot);
 
+        sleep 1000 // Some filesystems (ext3) have one-second granularity for lastModified, so bump the time to ensure we can detect a regenerated file
         executer.withArguments("--rerun-tasks").withTasks("jar").run();
 
         jar.assertHasChangedSince(snapshot);
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
index 2fe87e9..6bb21e8 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
@@ -21,12 +21,18 @@ import org.junit.Rule
 import org.junit.Test
 import org.gradle.integtests.fixtures.*
 import static org.hamcrest.Matchers.startsWith
+import org.junit.Before
 
 class IncrementalTestIntegrationTest {
     @Rule public final GradleDistribution distribution = new GradleDistribution()
     @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public final TestResources resources = new TestResources()
 
+    @Before
+    public void before() {
+        executer.allowExtraLogging = false
+    }
+
     @Test
     public void doesNotRunStaleTests() {
         def failure = executer.withTasks('test').runWithFailure()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy
deleted file mode 100644
index b06a182..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy
+++ /dev/null
@@ -1,470 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.integtests
-
-import junit.framework.AssertionFailedError
-import org.gradle.util.TestFile
-import org.junit.Rule
-import org.junit.Test
-import org.gradle.integtests.fixtures.*
-import org.gradle.internal.SystemProperties
-
-/**
- * @author Hans Dockter
- */
-class LoggingIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources resources = new TestResources()
-    @Rule public final Sample sampleResources = new Sample()
-
-    private final LogOutput logOutput = new LogOutput() {{
-        quiet(
-                'An info log message which is always logged.',
-                'A message which is logged at QUIET level',
-                'Text which is logged at QUIET level',
-                'A task message which is logged at QUIET level',
-                'quietProject2ScriptClassPathOut',
-                'quietProject2CallbackOut',
-                'settings quiet out',
-                'init QUIET out',
-                'init callback quiet out',
-                'main buildSrc quiet',
-                'nestedBuild buildSrc quiet',
-                'nestedBuild quiet',
-                'nestedBuild task quiet',
-                'external QUIET message')
-        error(
-                'An error log message.',
-                'An error message which is logged at ERROR level',
-                'external ERROR error message',
-                '[ant:echo] An error message logged from Ant',
-                'A severe log message logged using JUL',
-                'init ERROR err'
-        )
-        warning(
-                'A warning log message.',
-                'A task error message which is logged at WARN level',
-                '[ant:echo] A warn message logged from Ant',
-                'A warning log message logged using JUL'
-        )
-        lifecycle(
-                'A lifecycle info log message.',
-                'An error message which is logged at LIFECYCLE level',
-                'A task message which is logged at LIFECYCLE level',
-                'settings lifecycle log',
-                'init lifecycle log',
-                'external LIFECYCLE error message',
-                'external LIFECYCLE log message',
-                'LOGGER: evaluating :',
-                'LOGGER: evaluating :project1',
-                'LOGGER: evaluating :project2',
-                'LOGGER: executing :project1:logInfo',
-                'LOGGER: executing :project1:logLifecycle',
-                'LOGGER: executing :project1:nestedBuildLog',
-                'LOGGER: executing :project1:log',
-                ':nestedBuild:log'
-        )
-        info(
-                'An info log message.',
-                'A message which is logged at INFO level',
-                'Text which is logged at INFO level',
-                'A task message which is logged at INFO level',
-                '[ant:echo] An info message logged from Ant',
-                'An info log message logged using SLF4j',
-                'An info log message logged using JCL',
-                'An info log message logged using Log4j',
-                'An info log message logged using JUL',
-                'A config log message logged using JUL',
-                'infoProject2Out',
-                'infoProject2ScriptClassPathOut',
-                'settings info out',
-                'settings info log',
-                'init INFO out',
-                'init INFO err',
-                'init info log',
-                'LOGGER: build finished',
-                'LOGGER: evaluated project :',
-                'LOGGER: evaluated project :project1',
-                'LOGGER: evaluated project :project2',
-                'LOGGER: executed task :project1:log',
-                'LOGGER: executed task :project1:logInfo',
-                'LOGGER: executed task :project1:logLifecycle',
-                'LOGGER: task :project1:log starting work',
-                'LOGGER: task :project1:log completed work',
-                'main buildSrc info',
-                'nestedBuild buildSrc info',
-                'nestedBuild info',
-                'external INFO message'
-        )
-        debug(
-                'A debug log message.',
-                '[ant:echo] A debug message logged from Ant',
-                'A fine log message logged using JUL'
-        )
-        trace(
-                'A trace log message.'
-        )
-        forbidden(
-                // the default message generated by JUL
-                'INFO: An info log message logged using JUL',
-                // the custom logger should override this
-                'BUILD SUCCESSFUL'
-        )
-    }}
-
-    private final LogOutput sample = new LogOutput() {{
-        error('An error log message.')
-        quiet('An info log message which is always logged.')
-        quiet('A message which is logged at QUIET level')
-        warning('A warning log message.')
-        lifecycle('A lifecycle info log message.')
-        info('An info log message.')
-        info('A message which is logged at INFO level')
-        info('A task message which is logged at INFO level')
-        info('An info log message logged using SLF4j')
-        debug('A debug log message.')
-        forbidden('A trace log message.')
-    }}
-
-    private final LogOutput multiThreaded = new LogOutput() {{
-        (1..10).each { thread ->
-            (1..100).each { iteration ->
-                lifecycle("log message from thread $thread iteration $iteration")
-                quiet("stdout message from thread $thread iteration $iteration")
-                quiet("styled text message from thread $thread iteration $iteration")
-            }
-        }
-    }}
-
-    private final LogOutput brokenBuild = new LogOutput() {{
-        error('FAILURE: Could not determine which tasks to execute.')
-    }}
-
-    @Test
-    public void quietLogging() {
-        checkOutput(this.&run, logOutput.quiet)
-    }
-
-    @Test
-    public void lifecycleLogging() {
-        checkOutput(this.&run, logOutput.lifecycle)
-    }
-
-    @Test
-    public void infoLogging() {
-        checkOutput(this.&run, logOutput.info)
-    }
-
-    @Test
-    public void debugLogging() {
-        checkOutput(this.&run, logOutput.debug)
-    }
-
-    @Test
-    public void lifecycleLoggingForBrokenBuild() {
-        checkOutput(this.&runBroken, brokenBuild.lifecycle)
-    }
-
-    @Test @UsesSample('userguide/tutorial/logging')
-    public void sampleQuietLogging() {
-        checkOutput(this.&runSample, sample.quiet)
-    }
-
-    @Test @UsesSample('userguide/tutorial/logging')
-    public void sampleLifecycleLogging() {
-        checkOutput(this.&runSample, sample.lifecycle)
-    }
-
-    @Test @UsesSample('userguide/tutorial/logging')
-    public void sampleInfoLogging() {
-        checkOutput(this.&runSample, sample.info)
-    }
-
-    @Test @UsesSample('userguide/tutorial/logging')
-    public void sampleDebugLogging() {
-        checkOutput(this.&runSample, sample.debug)
-    }
-
-    @Test
-    public void multiThreadedQuietLogging() {
-        checkOutput(this.&runMultiThreaded, multiThreaded.quiet)
-    }
-
-    @Test
-    public void multiThreadedlifecycleLogging() {
-        checkOutput(this.&runMultiThreaded, multiThreaded.lifecycle)
-    }
-
-    @Test
-    public void multiThreadedDebugLogging() {
-        checkOutput(this.&runMultiThreaded, multiThreaded.debug)
-    }
-
-    def run(LogLevel level) {
-        resources.maybeCopy('LoggingIntegrationTest/logging')
-        TestFile loggingDir = dist.testDir
-        loggingDir.file("buildSrc/build/.gradle").deleteDir()
-        loggingDir.file("nestedBuild/buildSrc/.gradle").deleteDir()
-
-        String initScript = new File(loggingDir, 'init.gradle').absolutePath
-        String[] allArgs = level.args + ['-I', initScript]
-        return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withArguments(allArgs).withTasks('log').run()
-    }
-
-    def runBroken(LogLevel level) {
-        TestFile loggingDir = dist.testDir
-
-        return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withTasks('broken').runWithFailure()
-    }
-
-    def runMultiThreaded(LogLevel level) {
-        resources.maybeCopy('LoggingIntegrationTest/multiThreaded')
-        return executer.setAllowExtraLogging(false).withArguments(level.args).withTasks('log').run()
-    }
-
-    def runSample(LogLevel level) {
-        return executer.setAllowExtraLogging(false).inDirectory(sampleResources.dir).withArguments(level.args).withTasks('log').run()
-    }
-
-    void checkOutput(Closure run, LogLevel level) {
-        ExecutionResult result = run.call(level)
-        level.checkOuts(result)
-    }
-
-    @Test
-    public void deprecatedLogging() {
-        LogLevel deprecated = new LogLevel(
-            args: [],
-            infoMessages: [['A deprecation warning']],
-            errorMessages: [],
-            allMessages: []
-        )
-
-        resources.maybeCopy('LoggingIntegrationTest/deprecated')
-        ExecutionResult result = executer.withDeprecationChecksDisabled().withArguments(deprecated.args).withTasks('log').run()
-        deprecated.checkOuts(result)
-
-        // Ensure warnings are logged the second time
-        ExecutionResult secondResult = executer.withDeprecationChecksDisabled().withArguments(deprecated.args).withTasks('log').run()
-        deprecated.checkOuts(secondResult)
-    }
-
-    @Test
-    public void deprecatedLoggingIsNotDisplayedWithQuietFlag() {
-        LogLevel deprecated = new LogLevel(
-            args: ['-q'],
-            infoMessages: [],
-            errorMessages: [],
-            allMessages: [['A deprecation warning']]
-        )
-
-        resources.maybeCopy('LoggingIntegrationTest/deprecated')
-        ExecutionResult result = executer.withArguments(deprecated.args).withTasks('log').run()
-
-        deprecated.checkOuts(result)
-    }
-}
-
-class LogLevel {
-    List<String> args
-    List<String> infoMessages
-    List<String> errorMessages
-    List<String> allMessages
-    Closure validator = {OutputOccurrence occurrence ->
-        occurrence.assertIsAtEndOfLine()
-        occurrence.assertIsAtStartOfLine()
-    }
-
-    def getForbiddenMessages() {
-        allMessages - (infoMessages + errorMessages)
-    }
-
-    def checkOuts(ExecutionResult result) {
-        infoMessages.each {List<String> messages ->
-            checkOuts(true, result.output, messages, validator)
-        }
-        errorMessages.each {List<String> messages ->
-            checkOuts(true, result.error, messages, validator)
-        }
-        forbiddenMessages.each {List<String> messages ->
-            checkOuts(false, result.output, messages) {occurrence -> }
-            checkOuts(false, result.error, messages) {occurrence -> }
-        }
-    }
-
-    def checkOuts(boolean shouldContain, String result, List<String> outs, Closure validator) {
-        outs.each {String expectedOut ->
-            def filters = outs.findAll { other -> other != expectedOut && other.startsWith(expectedOut) }
-
-            // Find all locations of the expected string in the output
-            List<Integer> matches = []
-            int pos = 0;
-            while (pos < result.length()) {
-                int match = result.indexOf(expectedOut, pos)
-                if (match < 0) {
-                    break
-                }
-
-                // Filter matches with other expected strings that have this string as a prefix
-                boolean filter = filters.find { other -> result.substring(match).startsWith(other)} != null
-                if (!filter) {
-                    matches << match
-                }
-                pos = match + expectedOut.length()
-            }
-
-            // Check we found the expected number of occurrences of the expected string
-            if (!shouldContain) {
-                if (!matches.empty) {
-                    throw new AssertionFailedError("Found unexpected content '$expectedOut' in output:\n$result")
-                }
-            } else {
-                if (matches.empty) {
-                    throw new AssertionFailedError("Could not find expected content '$expectedOut' in output:\n$result")
-                }
-                if (matches.size() > 1) {
-                    throw new AssertionFailedError("Expected content '$expectedOut' should occur exactly once but found ${matches.size()} times in output:\n$result")
-                }
-
-                // Validate each occurrence
-                matches.each {
-                    validator.call(new OutputOccurrence(expectedOut, result, it))
-                }
-            }
-        }
-    }
-}
-
-class LogOutput {
-    final List<String> quietMessages = []
-    final List<String> errorMessages = []
-    final List<String> warningMessages = []
-    final List<String> lifecycleMessages = []
-    final List<String> infoMessages = []
-    final List<String> debugMessages = []
-    final List<String> traceMessages = []
-    final List<String> forbiddenMessages = []
-    final List<String> allOuts = [
-            errorMessages,
-            quietMessages,
-            warningMessages,
-            lifecycleMessages,
-            infoMessages,
-            debugMessages,
-            traceMessages,
-            forbiddenMessages
-    ]
-
-    def quiet(String... msgs) {
-        quietMessages.addAll(msgs)
-    }
-    def error(String... msgs) {
-        errorMessages.addAll(msgs)
-    }
-    def warning(String... msgs) {
-        warningMessages.addAll(msgs)
-    }
-    def lifecycle(String... msgs) {
-        warningMessages.addAll(msgs)
-    }
-    def info(String... msgs) {
-        infoMessages.addAll(msgs)
-    }
-    def debug(String... msgs) {
-        debugMessages.addAll(msgs)
-    }
-    def trace(String... msgs) {
-        traceMessages.addAll(msgs)
-    }
-    def forbidden(String... msgs) {
-        forbiddenMessages.addAll(msgs)
-    }
-
-    final LogLevel quiet = new LogLevel(
-            args: ['-q'],
-            infoMessages: [quietMessages],
-            errorMessages: [errorMessages],
-            allMessages: allOuts
-    )
-    final LogLevel lifecycle = new LogLevel(
-            args: [],
-            infoMessages: [quietMessages, warningMessages, lifecycleMessages],
-            errorMessages: [errorMessages],
-            allMessages: allOuts
-    )
-    final LogLevel info = new LogLevel(
-            args: ['-i'],
-            infoMessages: [quietMessages, warningMessages, lifecycleMessages, infoMessages],
-            errorMessages: [errorMessages],
-            allMessages: allOuts
-    )
-    final LogLevel debug = new LogLevel(
-            args: ['-d'],
-            infoMessages: [quietMessages, warningMessages, lifecycleMessages, infoMessages, debugMessages],
-            errorMessages: [errorMessages],
-            allMessages: allOuts,
-            validator: {OutputOccurrence occurrence ->
-                occurrence.assertIsAtEndOfLine()
-                occurrence.assertHasPrefix(/.+ \[.+\] \[.+\] /)
-            }
-    )
-}
-
-class OutputOccurrence {
-    final String expected
-    final String actual
-    final int index
-
-    OutputOccurrence(String expected, String actual, int index) {
-        this.expected = expected
-        this.actual = actual
-        this.index = index
-    }
-
-    void assertIsAtStartOfLine() {
-        if (index == 0) {
-            return
-        }
-        int startLine = index - SystemProperties.lineSeparator.length()
-        if (startLine < 0 || !actual.substring(startLine).startsWith(SystemProperties.lineSeparator)) {
-            throw new AssertionFailedError("Expected content '$expected' is not at the start of a line in output $actual.")
-        }
-    }
-
-    void assertIsAtEndOfLine() {
-        int endLine = index + expected.length()
-        if (endLine == actual.length()) {
-            return
-        }
-        if (!actual.substring(endLine).startsWith(SystemProperties.lineSeparator)) {
-            throw new AssertionFailedError("Expected content '$expected' is not at the end of a line in output $actual.")
-        }
-    }
-
-    void assertHasPrefix(String pattern) {
-        int startLine = actual.lastIndexOf(SystemProperties.lineSeparator, index)
-        if (startLine < 0) {
-            startLine = 0
-        } else {
-            startLine += SystemProperties.lineSeparator.length()
-        }
-        
-        String actualPrefix = actual.substring(startLine, index)
-        assert actualPrefix.matches(pattern): "Unexpected prefix '$actualPrefix' found for line containing content '$expected' in output $actual"
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
index 81486f3..cc8cfa3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
@@ -52,7 +52,7 @@ class OsgiProjectSampleIntegrationTest {
         assertEquals('2', manifest.mainAttributes.getValue('Bundle-ManifestVersion'))
         assertEquals('Bnd-1.50.0', manifest.mainAttributes.getValue('Tool'))
         assertTrue(start <= Long.parseLong(manifest.mainAttributes.getValue('Bnd-LastModified')))
-        assertEquals('1.0', manifest.mainAttributes.getValue('Bundle-Version'))
+        assertEquals('1.0.0', manifest.mainAttributes.getValue('Bundle-Version'))
         assertEquals('gradle_tooling.osgi', manifest.mainAttributes.getValue('Bundle-SymbolicName'))
         assertEquals( GradleVersion.current().version, manifest.mainAttributes.getValue('Built-By'))
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
index a9efcc6..e4c19d3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
@@ -28,6 +28,7 @@ import org.gradle.api.internal.file.BaseDirFileResolver;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.cache.CacheRepository;
 import org.gradle.cache.internal.*;
+import org.gradle.internal.id.LongIdGenerator;
 import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.listener.ListenerBroadcast;
@@ -39,7 +40,6 @@ import org.gradle.messaging.remote.internal.MessagingServices;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.process.internal.*;
 import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
-import org.gradle.util.LongIdGenerator;
 import org.gradle.util.TemporaryFolder;
 import org.jmock.Expectations;
 import org.jmock.Sequence;
@@ -50,6 +50,9 @@ import org.junit.runner.RunWith;
 
 import java.io.ObjectInputStream;
 import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -160,17 +163,13 @@ public class WorkerProcessIntegrationTest {
 
     @Test
     public void handlesWorkerProcessWhenJvmFailsToStart() throws Throwable {
-        execute(mainClass("no-such-class").expectStartFailure());
+        execute(worker(new NoOpAction()).jvmArgs("--broken").expectStartFailure());
     }
 
     private ChildProcess worker(Action<WorkerProcessContext> action) {
         return new ChildProcess(action);
     }
 
-    private ChildProcess mainClass(String mainClass) {
-        return new ChildProcess(new NoOpAction()).mainClass(mainClass);
-    }
-
     void execute(ChildProcess... processes) throws Throwable {
         for (ChildProcess process : processes) {
             process.start();
@@ -187,7 +186,7 @@ public class WorkerProcessIntegrationTest {
         private boolean startFails;
         private WorkerProcess proc;
         private Action<WorkerProcessContext> action;
-        private String mainClass;
+        private List<String> jvmArgs = Collections.emptyList();
         private Action<ObjectConnection> serverAction;
 
         public ChildProcess(Action<WorkerProcessContext> action) {
@@ -212,9 +211,7 @@ public class WorkerProcessIntegrationTest {
             builder.getJavaCommand().environment("TEST_ENV_VAR", "value");
             builder.worker(action);
 
-            if (mainClass != null) {
-                builder.getJavaCommand().setMain(mainClass);
-            }
+            builder.getJavaCommand().jvmArgs(jvmArgs);
 
             proc = builder.build();
             try {
@@ -242,13 +239,13 @@ public class WorkerProcessIntegrationTest {
             }
         }
 
-        public ChildProcess mainClass(String mainClass) {
-            this.mainClass = mainClass;
+        public ChildProcess onServer(Action<ObjectConnection> action) {
+            this.serverAction = action;
             return this;
         }
 
-        public ChildProcess onServer(Action<ObjectConnection> action) {
-            this.serverAction = action;
+        public ChildProcess jvmArgs(String... jvmArgs) {
+            this.jvmArgs = Arrays.asList(jvmArgs);
             return this;
         }
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/SingleUseDaemonIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/SingleUseDaemonIntegrationTest.groovy
deleted file mode 100644
index a212ef1..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/environment/SingleUseDaemonIntegrationTest.groovy
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.integtests.environment
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.AvailableJavaHomes
-import org.gradle.util.TextUtil
-import spock.lang.IgnoreIf
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-
- at IgnoreIf({ GradleDistributionExecuter.systemPropertyExecuter == GradleDistributionExecuter.Executer.daemon })
-class SingleUseDaemonIntegrationTest extends AbstractIntegrationSpec {
-    def setup() {
-        // Need forking executer
-        // '-ea' is always set on the forked process. So I've added it explicitly here. // TODO:DAZ Clean this up
-        executer.withForkingExecuter().withEnvironmentVars(["JAVA_OPTS": "-ea"])
-        distribution.requireIsolatedDaemons()
-    }
-
-    def "stops single use daemon on build complete"() {
-        requireJvmArg('-Xmx32m')
-
-        file('build.gradle') << "println 'hello world'"
-
-        when:
-        succeeds()
-
-        then:
-        wasForked()
-        and:
-        executer.getDaemonRegistry().all.empty
-    }
-
-    def "stops single use daemon when build fails"() {
-        requireJvmArg('-Xmx32m')
-
-        file('build.gradle') << "throw new RuntimeException('bad')"
-
-        when:
-        fails()
-
-        then:
-        wasForked()
-        failureHasCause "bad"
-
-        and:
-        executer.getDaemonRegistry().all.empty
-    }
-
-    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
-    def "does not fork build if java home from gradle properties matches current process"() {
-        def alternateJavaHome = AvailableJavaHomes.bestAlternative
-
-        file('gradle.properties') << "org.gradle.java.home=${TextUtil.escapeString(alternateJavaHome.canonicalPath)}"
-
-        file('build.gradle') << "println 'javaHome=' + org.gradle.internal.jvm.Jvm.current().javaHome.absolutePath"
-
-        when:
-        executer.withJavaHome(alternateJavaHome)
-        succeeds()
-
-        then:
-        !wasForked();
-    }
-
-    def "forks build to run when immutable jvm args set regardless of the environment"() {
-        when:
-        requireJvmArg('-Xmx32m')
-        runWithJvmArg('-Xmx32m')
-
-        and:
-        file('build.gradle') << """
-assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx32m')
-"""
-
-        then:
-        succeeds()
-
-        and:
-        wasForked()
-    }
-
-    def "does not fork build and configures system properties from gradle properties"() {
-        when:
-        requireJvmArg('-Dsome-prop=some-value')
-
-        and:
-        file('build.gradle') << """
-assert System.getProperty('some-prop') == 'some-value'
-"""
-
-        then:
-        succeeds()
-
-        and:
-        !wasForked()
-    }
-
-    private def requireJvmArg(String jvmArg) {
-        file('gradle.properties') << "org.gradle.jvmargs=$jvmArg"
-    }
-
-    private def runWithJvmArg(String jvmArg) {
-        executer.withEnvironmentVars(["JAVA_OPTS": "$jvmArg -ea"])
-    }
-
-    private def wasForked() {
-        result.output.contains('fork a new JVM')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy
new file mode 100644
index 0000000..ac6a4b1
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/fixture/M2Installation.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.fixture
+
+import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.MavenRepository
+
+class M2Installation {
+    final TestFile userM2Directory
+    final TestFile userSettingsFile
+    TestFile globalMavenDirectory = null;
+
+    public M2Installation(TestFile m2Directory) {
+        this.userM2Directory = m2Directory;
+        this.userSettingsFile = m2Directory.file("settings.xml")
+    }
+
+    MavenRepository mavenRepo() {
+        mavenRepo(userM2Directory.file("repository"))
+    }
+
+    MavenRepository mavenRepo(TestFile file) {
+        new MavenRepository(file)
+    }
+
+    TestFile createGlobalSettingsFile(TestFile globalMavenDirectory) {
+        this.globalMavenDirectory = globalMavenDirectory;
+        globalMavenDirectory.file("conf/settings.xml").createFile()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest.groovy
new file mode 100644
index 0000000..4ac5aaa
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.logging
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+import spock.lang.Unroll
+
+class LoggerIsEnabledIntegrationTest extends AbstractIntegrationSpec {
+    @Rule TestResources resources
+
+    @Unroll
+    def "logger.isEnabled() works correctly for log level #level"() {
+        executer.withArguments("-Plevel=$level")
+
+        expect:
+        succeeds("checkLevel")
+
+        where:
+        level << org.gradle.api.logging.LogLevel.values()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
new file mode 100644
index 0000000..0c861c3
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
@@ -0,0 +1,469 @@
+/*
+ * Copyright 2010 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 org.gradle.integtests.logging
+
+import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.*
+import org.gradle.internal.SystemProperties
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+class LoggingIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+    @Rule public final Sample sampleResources = new Sample()
+
+    private final LogOutput logOutput = new LogOutput() {{
+        quiet(
+                'An info log message which is always logged.',
+                'A message which is logged at QUIET level',
+                'Text which is logged at QUIET level',
+                'A task message which is logged at QUIET level',
+                'quietProject2ScriptClassPathOut',
+                'quietProject2CallbackOut',
+                'settings quiet out',
+                'init QUIET out',
+                'init callback quiet out',
+                'main buildSrc quiet',
+                'nestedBuild buildSrc quiet',
+                'nestedBuild quiet',
+                'nestedBuild task quiet',
+                'external QUIET message')
+        error(
+                'An error log message.',
+                'An error message which is logged at ERROR level',
+                'external ERROR error message',
+                '[ant:echo] An error message logged from Ant',
+                'A severe log message logged using JUL',
+                'init ERROR err'
+        )
+        warning(
+                'A warning log message.',
+                'A task error message which is logged at WARN level',
+                '[ant:echo] A warn message logged from Ant',
+                'A warning log message logged using JUL'
+        )
+        lifecycle(
+                'A lifecycle info log message.',
+                'An error message which is logged at LIFECYCLE level',
+                'A task message which is logged at LIFECYCLE level',
+                'settings lifecycle log',
+                'init lifecycle log',
+                'external LIFECYCLE error message',
+                'external LIFECYCLE log message',
+                'LOGGER: evaluating :',
+                'LOGGER: evaluating :project1',
+                'LOGGER: evaluating :project2',
+                'LOGGER: executing :project1:logInfo',
+                'LOGGER: executing :project1:logLifecycle',
+                'LOGGER: executing :project1:nestedBuildLog',
+                'LOGGER: executing :project1:log',
+                ':nestedBuild:log'
+        )
+        info(
+                'An info log message.',
+                'A message which is logged at INFO level',
+                'Text which is logged at INFO level',
+                'A task message which is logged at INFO level',
+                '[ant:echo] An info message logged from Ant',
+                'An info log message logged using SLF4j',
+                'An info log message logged using JCL',
+                'An info log message logged using Log4j',
+                'An info log message logged using JUL',
+                'A config log message logged using JUL',
+                'infoProject2Out',
+                'infoProject2ScriptClassPathOut',
+                'settings info out',
+                'settings info log',
+                'init INFO out',
+                'init INFO err',
+                'init info log',
+                'LOGGER: build finished',
+                'LOGGER: evaluated project :',
+                'LOGGER: evaluated project :project1',
+                'LOGGER: evaluated project :project2',
+                'LOGGER: executed task :project1:log',
+                'LOGGER: executed task :project1:logInfo',
+                'LOGGER: executed task :project1:logLifecycle',
+                'LOGGER: task :project1:log starting work',
+                'LOGGER: task :project1:log completed work',
+                'main buildSrc info',
+                'nestedBuild buildSrc info',
+                'nestedBuild info',
+                'external INFO message'
+        )
+        debug(
+                'A debug log message.',
+                '[ant:echo] A debug message logged from Ant',
+                'A fine log message logged using JUL'
+        )
+        trace(
+                'A trace log message.'
+        )
+        forbidden(
+                // the default message generated by JUL
+                'INFO: An info log message logged using JUL',
+                // the custom logger should override this
+                'BUILD SUCCESSFUL'
+        )
+    }}
+
+    private final LogOutput sample = new LogOutput() {{
+        error('An error log message.')
+        quiet('An info log message which is always logged.')
+        quiet('A message which is logged at QUIET level')
+        warning('A warning log message.')
+        lifecycle('A lifecycle info log message.')
+        info('An info log message.')
+        info('A message which is logged at INFO level')
+        info('A task message which is logged at INFO level')
+        info('An info log message logged using SLF4j')
+        debug('A debug log message.')
+        forbidden('A trace log message.')
+    }}
+
+    private final LogOutput multiThreaded = new LogOutput() {{
+        (1..10).each { thread ->
+            (1..100).each { iteration ->
+                lifecycle("log message from thread $thread iteration $iteration")
+                quiet("stdout message from thread $thread iteration $iteration")
+                quiet("styled text message from thread $thread iteration $iteration")
+            }
+        }
+    }}
+
+    private final LogOutput brokenBuild = new LogOutput() {{
+        error('FAILURE: Could not determine which tasks to execute.')
+    }}
+
+    @Test
+    public void quietLogging() {
+        checkOutput(this.&run, logOutput.quiet)
+    }
+
+    @Test
+    public void lifecycleLogging() {
+        checkOutput(this.&run, logOutput.lifecycle)
+    }
+
+    @Test
+    public void infoLogging() {
+        checkOutput(this.&run, logOutput.info)
+    }
+
+    @Test
+    public void debugLogging() {
+        checkOutput(this.&run, logOutput.debug)
+    }
+
+    @Test
+    public void lifecycleLoggingForBrokenBuild() {
+        checkOutput(this.&runBroken, brokenBuild.lifecycle)
+    }
+
+    @Test @UsesSample('userguide/tutorial/logging')
+    public void sampleQuietLogging() {
+        checkOutput(this.&runSample, sample.quiet)
+    }
+
+    @Test @UsesSample('userguide/tutorial/logging')
+    public void sampleLifecycleLogging() {
+        checkOutput(this.&runSample, sample.lifecycle)
+    }
+
+    @Test @UsesSample('userguide/tutorial/logging')
+    public void sampleInfoLogging() {
+        checkOutput(this.&runSample, sample.info)
+    }
+
+    @Test @UsesSample('userguide/tutorial/logging')
+    public void sampleDebugLogging() {
+        checkOutput(this.&runSample, sample.debug)
+    }
+
+    @Test
+    public void multiThreadedQuietLogging() {
+        checkOutput(this.&runMultiThreaded, multiThreaded.quiet)
+    }
+
+    @Test
+    public void multiThreadedlifecycleLogging() {
+        checkOutput(this.&runMultiThreaded, multiThreaded.lifecycle)
+    }
+
+    @Test
+    public void multiThreadedDebugLogging() {
+        checkOutput(this.&runMultiThreaded, multiThreaded.debug)
+    }
+
+    def run(LogLevel level) {
+        resources.maybeCopy('LoggingIntegrationTest/logging')
+        TestFile loggingDir = dist.testDir
+        loggingDir.file("buildSrc/build/.gradle").deleteDir()
+        loggingDir.file("nestedBuild/buildSrc/.gradle").deleteDir()
+
+        String initScript = new File(loggingDir, 'init.gradle').absolutePath
+        String[] allArgs = level.args + ['-I', initScript]
+        return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withArguments(allArgs).withTasks('log').run()
+    }
+
+    def runBroken(LogLevel level) {
+        TestFile loggingDir = dist.testDir
+
+        return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withTasks('broken').runWithFailure()
+    }
+
+    def runMultiThreaded(LogLevel level) {
+        resources.maybeCopy('LoggingIntegrationTest/multiThreaded')
+        return executer.setAllowExtraLogging(false).withArguments(level.args).withTasks('log').run()
+    }
+
+    def runSample(LogLevel level) {
+        return executer.setAllowExtraLogging(false).inDirectory(sampleResources.dir).withArguments(level.args).withTasks('log').run()
+    }
+
+    void checkOutput(Closure run, LogLevel level) {
+        ExecutionResult result = run.call(level)
+        level.checkOuts(result)
+    }
+
+    @Test
+    public void deprecatedLogging() {
+        LogLevel deprecated = new LogLevel(
+            args: [],
+            infoMessages: [['A deprecation warning']],
+            errorMessages: [],
+            allMessages: []
+        )
+
+        resources.maybeCopy('LoggingIntegrationTest/deprecated')
+        ExecutionResult result = executer.withDeprecationChecksDisabled().withArguments(deprecated.args).withTasks('log').run()
+        deprecated.checkOuts(result)
+
+        // Ensure warnings are logged the second time
+        ExecutionResult secondResult = executer.withDeprecationChecksDisabled().withArguments(deprecated.args).withTasks('log').run()
+        deprecated.checkOuts(secondResult)
+    }
+
+    @Test
+    public void deprecatedLoggingIsNotDisplayedWithQuietFlag() {
+        LogLevel deprecated = new LogLevel(
+            args: ['-q'],
+            infoMessages: [],
+            errorMessages: [],
+            allMessages: [['A deprecation warning']]
+        )
+
+        resources.maybeCopy('LoggingIntegrationTest/deprecated')
+        ExecutionResult result = executer.withArguments(deprecated.args).withTasks('log').run()
+
+        deprecated.checkOuts(result)
+    }
+}
+
+class LogLevel {
+    List<String> args
+    List<String> infoMessages
+    List<String> errorMessages
+    List<String> allMessages
+    Closure validator = {OutputOccurrence occurrence ->
+        occurrence.assertIsAtEndOfLine()
+        occurrence.assertIsAtStartOfLine()
+    }
+
+    def getForbiddenMessages() {
+        allMessages - (infoMessages + errorMessages)
+    }
+
+    def checkOuts(ExecutionResult result) {
+        infoMessages.each {List<String> messages ->
+            checkOuts(true, result.output, messages, validator)
+        }
+        errorMessages.each {List<String> messages ->
+            checkOuts(true, result.error, messages, validator)
+        }
+        forbiddenMessages.each {List<String> messages ->
+            checkOuts(false, result.output, messages) {occurrence -> }
+            checkOuts(false, result.error, messages) {occurrence -> }
+        }
+    }
+
+    def checkOuts(boolean shouldContain, String result, List<String> outs, Closure validator) {
+        outs.each {String expectedOut ->
+            def filters = outs.findAll { other -> other != expectedOut && other.startsWith(expectedOut) }
+
+            // Find all locations of the expected string in the output
+            List<Integer> matches = []
+            int pos = 0;
+            while (pos < result.length()) {
+                int match = result.indexOf(expectedOut, pos)
+                if (match < 0) {
+                    break
+                }
+
+                // Filter matches with other expected strings that have this string as a prefix
+                boolean filter = filters.find { other -> result.substring(match).startsWith(other)} != null
+                if (!filter) {
+                    matches << match
+                }
+                pos = match + expectedOut.length()
+            }
+
+            // Check we found the expected number of occurrences of the expected string
+            if (!shouldContain) {
+                if (!matches.empty) {
+                    throw new AssertionError("Found unexpected content '$expectedOut' in output:\n$result")
+                }
+            } else {
+                if (matches.empty) {
+                    throw new AssertionError("Could not find expected content '$expectedOut' in output:\n$result")
+                }
+                if (matches.size() > 1) {
+                    throw new AssertionError("Expected content '$expectedOut' should occur exactly once but found ${matches.size()} times in output:\n$result")
+                }
+
+                // Validate each occurrence
+                matches.each {
+                    validator.call(new OutputOccurrence(expectedOut, result, it))
+                }
+            }
+        }
+    }
+}
+
+class LogOutput {
+    final List<String> quietMessages = []
+    final List<String> errorMessages = []
+    final List<String> warningMessages = []
+    final List<String> lifecycleMessages = []
+    final List<String> infoMessages = []
+    final List<String> debugMessages = []
+    final List<String> traceMessages = []
+    final List<String> forbiddenMessages = []
+    final List<String> allOuts = [
+            errorMessages,
+            quietMessages,
+            warningMessages,
+            lifecycleMessages,
+            infoMessages,
+            debugMessages,
+            traceMessages,
+            forbiddenMessages
+    ]
+
+    def quiet(String... msgs) {
+        quietMessages.addAll(msgs)
+    }
+    def error(String... msgs) {
+        errorMessages.addAll(msgs)
+    }
+    def warning(String... msgs) {
+        warningMessages.addAll(msgs)
+    }
+    def lifecycle(String... msgs) {
+        warningMessages.addAll(msgs)
+    }
+    def info(String... msgs) {
+        infoMessages.addAll(msgs)
+    }
+    def debug(String... msgs) {
+        debugMessages.addAll(msgs)
+    }
+    def trace(String... msgs) {
+        traceMessages.addAll(msgs)
+    }
+    def forbidden(String... msgs) {
+        forbiddenMessages.addAll(msgs)
+    }
+
+    final LogLevel quiet = new LogLevel(
+            args: ['-q'],
+            infoMessages: [quietMessages],
+            errorMessages: [errorMessages],
+            allMessages: allOuts
+    )
+    final LogLevel lifecycle = new LogLevel(
+            args: [],
+            infoMessages: [quietMessages, warningMessages, lifecycleMessages],
+            errorMessages: [errorMessages],
+            allMessages: allOuts
+    )
+    final LogLevel info = new LogLevel(
+            args: ['-i'],
+            infoMessages: [quietMessages, warningMessages, lifecycleMessages, infoMessages],
+            errorMessages: [errorMessages],
+            allMessages: allOuts
+    )
+    final LogLevel debug = new LogLevel(
+            args: ['-d'],
+            infoMessages: [quietMessages, warningMessages, lifecycleMessages, infoMessages, debugMessages],
+            errorMessages: [errorMessages],
+            allMessages: allOuts,
+            validator: {OutputOccurrence occurrence ->
+                occurrence.assertIsAtEndOfLine()
+                occurrence.assertHasPrefix(/.+ \[.+\] \[.+\] /)
+            }
+    )
+}
+
+class OutputOccurrence {
+    final String expected
+    final String actual
+    final int index
+
+    OutputOccurrence(String expected, String actual, int index) {
+        this.expected = expected
+        this.actual = actual
+        this.index = index
+    }
+
+    void assertIsAtStartOfLine() {
+        if (index == 0) {
+            return
+        }
+        int startLine = index - SystemProperties.lineSeparator.length()
+        if (startLine < 0 || !actual.substring(startLine).startsWith(SystemProperties.lineSeparator)) {
+            throw new AssertionError("Expected content '$expected' is not at the start of a line in output $actual.")
+        }
+    }
+
+    void assertIsAtEndOfLine() {
+        int endLine = index + expected.length()
+        if (endLine == actual.length()) {
+            return
+        }
+        if (!actual.substring(endLine).startsWith(SystemProperties.lineSeparator)) {
+            throw new AssertionError("Expected content '$expected' is not at the end of a line in output $actual.")
+        }
+    }
+
+    void assertHasPrefix(String pattern) {
+        int startLine = actual.lastIndexOf(SystemProperties.lineSeparator, index)
+        if (startLine < 0) {
+            startLine = 0
+        } else {
+            startLine += SystemProperties.lineSeparator.length()
+        }
+        
+        String actualPrefix = actual.substring(startLine, index)
+        assert actualPrefix.matches(pattern): "Unexpected prefix '$actualPrefix' found for line containing content '$expected' in output $actual"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
new file mode 100644
index 0000000..a6d6d64
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.IvyModule
+import org.gradle.integtests.fixtures.IvyRepository
+import org.gradle.util.Jvm
+import org.gradle.util.TestFile
+import org.gradle.util.TextUtil
+import org.hamcrest.Matchers
+import org.junit.Rule
+import org.mortbay.jetty.HttpStatus
+import spock.lang.Unroll
+
+public class IvyHttpPublishIntegrationTest extends AbstractIntegrationSpec {
+    private static final String BAD_CREDENTIALS = '''
+credentials {
+    username 'testuser'
+    password 'bad'
+}
+'''
+    @Rule
+    public final HttpServer server = new HttpServer()
+
+    private IvyModule module
+
+    def setup() {
+        def repo = new IvyRepository(distribution.testFile('ivy-repo'))
+        module = repo.module("org.gradle", "publish", "2")
+        module.moduleDir.mkdirs()
+    }
+
+    public void canPublishToUnauthenticatedHttpRepository() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            url "http://localhost:${server.port}"
+        }
+    }
+}
+"""
+        when:
+        expectUpload('/org.gradle/publish/2/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, HttpStatus.ORDINAL_201_Created)
+
+        and:
+        succeeds 'uploadArchives'
+
+        then:
+        module.ivyFile.assertIsFile()
+        module.assertChecksumPublishedFor(module.ivyFile)
+
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+        module.assertChecksumPublishedFor(module.jarFile)
+    }
+
+    @Unroll
+    def "can publish to authenticated repository using #authScheme auth"() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            credentials {
+                username 'testuser'
+                password 'password'
+            }
+            url "http://localhost:${server.port}"
+        }
+    }
+}
+"""
+
+        when:
+        server.authenticationScheme = authScheme
+        expectUpload('/org.gradle/publish/2/publish-2.jar', module, module.jarFile, 'testuser', 'password')
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, 'testuser', 'password')
+
+        then:
+        succeeds 'uploadArchives'
+
+        and:
+        module.ivyFile.assertIsFile()
+        module.assertChecksumPublishedFor(module.ivyFile)
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+        module.assertChecksumPublishedFor(module.jarFile)
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    def "reports failure publishing with #credsName credentials to authenticated repository using #authScheme auth"() {
+        given:
+        server.start()
+
+        when:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            $creds
+            url "http://localhost:${server.port}"
+        }
+    }
+}
+"""
+
+        and:
+        server.authenticationScheme = authScheme
+        server.allowPut('/org.gradle/publish/2/publish-2.jar', 'testuser', 'password')
+
+        then:
+        fails 'uploadArchives'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        failure.assertHasCause('Could not publish configuration \':archives\'.')
+        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+
+        where:
+        authScheme                   | credsName | creds
+        HttpServer.AuthScheme.BASIC  | 'empty'   | ''
+        HttpServer.AuthScheme.DIGEST | 'empty'   | ''
+        HttpServer.AuthScheme.BASIC  | 'bad'     | BAD_CREDENTIALS
+        HttpServer.AuthScheme.DIGEST | 'bad'     | BAD_CREDENTIALS
+    }
+
+    public void reportsFailedPublishToHttpRepository() {
+        given:
+        server.start()
+        def repositoryUrl = "http://localhost:${server.port}"
+
+        buildFile << """
+apply plugin: 'java'
+uploadArchives {
+    repositories {
+        ivy {
+            url "${repositoryUrl}"
+        }
+    }
+}
+"""
+
+        when:
+        server.addBroken("/")
+
+        then:
+        fails 'uploadArchives'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        failure.assertHasCause('Could not publish configuration \':archives\'.')
+        failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
+
+        when:
+        server.stop()
+
+        then:
+        fails 'uploadArchives'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':uploadArchives\'.')
+        failure.assertHasCause('Could not publish configuration \':archives\'.')
+        failure.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
+    }
+
+    public void usesFirstConfiguredPatternForPublication() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+    apply plugin: 'java'
+    version = '2'
+    group = 'org.gradle'
+    uploadArchives {
+        repositories {
+            ivy {
+                artifactPattern "http://localhost:${server.port}/primary/[module]/[artifact]-[revision].[ext]"
+                artifactPattern "http://localhost:${server.port}/alternative/[module]/[artifact]-[revision].[ext]"
+                ivyPattern "http://localhost:${server.port}/primary-ivy/[module]/ivy-[revision].xml"
+                ivyPattern "http://localhost:${server.port}/secondary-ivy/[module]/ivy-[revision].xml"
+            }
+        }
+    }
+    """
+
+        when:
+        expectUpload('/primary/publish/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
+        expectUpload('/primary-ivy/publish/ivy-2.xml', module, module.ivyFile)
+
+        then:
+        succeeds 'uploadArchives'
+
+        and:
+        module.ivyFile.assertIsFile()
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+    }
+
+    public void "can publish large artifact (tools.jar) to authenticated repository"() {
+        given:
+        server.start()
+        def toolsJar = TextUtil.escapeString(Jvm.current().toolsJar)
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'base'
+version = '2'
+group = 'org.gradle'
+
+configurations {
+    tools
+}
+artifacts {
+    tools(file('$toolsJar')) {
+        name 'tools'
+    }
+}
+
+uploadTools {
+    repositories {
+        ivy {
+            credentials {
+                username 'testuser'
+                password 'password'
+            }
+            url "http://localhost:${server.port}"
+        }
+    }
+}
+"""
+
+        when:
+        def uploadedToolsJar = module.moduleDir.file('toolsJar')
+        expectUpload('/org.gradle/publish/2/tools-2.jar', module, uploadedToolsJar, 'testuser', 'password')
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, 'testuser', 'password')
+
+        then:
+        succeeds 'uploadTools'
+
+        and:
+        module.ivyFile.assertIsFile()
+        uploadedToolsJar.assertIsCopyOf(new TestFile(toolsJar));
+
+    }
+
+    public void "does not upload meta-data file if artifact upload fails"() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            url "http://localhost:${server.port}"
+        }
+    }
+}
+"""
+        when:
+        server.expectPut("/org.gradle/publish/2/publish-2.jar", module.jarFile, HttpStatus.ORDINAL_500_Internal_Server_Error)
+
+        then:
+        fails 'uploadArchives'
+
+        and:
+        module.jarFile.assertExists()
+        module.ivyFile.assertDoesNotExist()
+    }
+
+    private void expectUpload(String path, IvyModule module, TestFile file, int statusCode = HttpStatus.ORDINAL_200_OK) {
+        server.expectPut(path, file, statusCode)
+        server.expectPut("${path}.sha1", module.sha1File(file), statusCode)
+    }
+
+    private void expectUpload(String path, IvyModule module, TestFile file, String username, String password) {
+        server.expectPut(path, username, password, file)
+        server.expectPut("${path}.sha1", username, password, module.sha1File(file))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy
new file mode 100644
index 0000000..7a5b329
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyLocalPublishIntegrationTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.IvyRepository
+import org.spockframework.util.TextUtil
+import spock.lang.Issue
+
+public class IvyLocalPublishIntegrationTest extends AbstractIntegrationSpec {
+    public void canPublishToLocalFileRepository() {
+        given:
+        def repo = new IvyRepository(distribution.testFile('ivy-repo'))
+        def module = repo.module("org.gradle", "publish", "2")
+
+        def rootDir = TextUtil.escape(repo.rootDir.path)
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+apply plugin: 'java'
+version = '2'
+group = 'org.gradle'
+uploadArchives {
+    repositories {
+        ivy {
+            url "${rootDir}"
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        module.ivyFile.assertIsFile()
+        module.assertChecksumPublishedFor(module.ivyFile)
+
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+        module.assertChecksumPublishedFor(module.jarFile)
+    }
+
+    @Issue("GRADLE-1811")
+    public void canGenerateTheIvyXmlWithoutPublishing() {
+        //this is more like documenting the current behavior.
+        //Down the road we should add explicit task to create ivy.xml file
+
+        given:
+        buildFile << '''
+apply plugin: 'java'
+
+configurations {
+  myJars
+}
+
+task myJar(type: Jar)
+
+artifacts {
+  'myJars' myJar
+}
+
+task ivyXml(type: Upload) {
+  descriptorDestination = file('ivy.xml')
+  uploadDescriptor = true
+  configuration = configurations.myJars
+}
+'''
+        when:
+        succeeds 'ivyXml'
+
+        then:
+        file('ivy.xml').assertIsFile()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyPublishIntegrationTest.groovy
deleted file mode 100644
index 34ff389..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.publish.ivy
-
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.HttpServer
-import org.hamcrest.Matchers
-import org.junit.Rule
-import org.junit.Test
-import org.mortbay.jetty.HttpStatus
-import spock.lang.Issue
-
-public class IvyPublishIntegrationTest {
-    @Rule
-    public final GradleDistribution dist = new GradleDistribution()
-    @Rule
-    public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule
-    public final HttpServer server = new HttpServer()
-
-    @Test
-    public void canPublishToLocalFileRepository() {
-        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
-        dist.testFile("build.gradle") << '''
-apply plugin: 'java'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    repositories {
-        ivy {
-            url "build/repo/"
-        }
-    }
-}
-'''
-        executer.withTasks("uploadArchives").run()
-
-        def uploadedJar = dist.testFile('build/repo/org.gradle/publish/2/publish-2.jar')
-        def uploadedIvy = dist.testFile('build/repo/org.gradle/publish/2/ivy-2.xml')
-        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
-        uploadedIvy.assertIsFile()
-    }
-
-    @Issue("GRADLE-1811")
-    @Test
-    public void canGenerateTheIvyXmlWithoutPublishing() {
-        //this is more like documenting the current behavior.
-        //Down the road we should add explicit task to create ivy.xml file
-
-        //given
-        dist.testFile("build.gradle") << '''
-apply plugin: 'java'
-
-configurations {
-  myJars
-}
-
-task myJar(type: Jar)
-
-artifacts {
-  'myJars' myJar
-}
-
-task ivyXml(type: Upload) {
-  descriptorDestination = file('ivy.xml')
-  uploadDescriptor = true
-  configuration = configurations.myJars
-}
-'''
-        //when
-        executer.withTasks("ivyXml").run()
-
-        //then
-        def ivyXml = dist.file('ivy.xml')
-        ivyXml.assertIsFile()
-    }
-
-    @Test
-    public void canPublishToUnauthenticatedHttpRepository() {
-        server.start()
-
-        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
-        dist.testFile("build.gradle") << """
-apply plugin: 'java'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    repositories {
-        ivy {
-            url "http://localhost:${server.port}"
-        }
-    }
-}
-"""
-        def uploadedJar = dist.testFile('uploaded.jar')
-        def uploadedIvy = dist.testFile('uploaded.xml')
-        server.expectPut('/org.gradle/publish/2/publish-2.jar', uploadedJar, HttpStatus.ORDINAL_200_OK)
-        server.expectPut('/org.gradle/publish/2/ivy-2.xml', uploadedIvy, HttpStatus.ORDINAL_201_Created)
-
-        executer.withTasks("uploadArchives").run()
-
-        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
-        uploadedIvy.assertIsFile()
-    }
-
-    @Test
-    public void canPublishToAuthenticatedHttpRepository() {
-        server.start()
-
-        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
-        dist.testFile("build.gradle") << """
-apply plugin: 'java'
-version = '2'
-group = 'org.gradle'
-uploadArchives {
-    repositories {
-        ivy {
-            credentials {
-                username 'user'
-                password 'password'
-            }
-            url "http://localhost:${server.port}"
-        }
-    }
-}
-"""
-
-        def uploadedJar = dist.testFile('uploaded.jar')
-        def uploadedIvy = dist.testFile('uploaded.xml')
-        server.expectPut('/org.gradle/publish/2/publish-2.jar', 'user', 'password', uploadedJar)
-        server.expectPut('/org.gradle/publish/2/ivy-2.xml', 'user', 'password', uploadedIvy)
-
-        executer.withTasks("uploadArchives").run()
-
-        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
-        uploadedIvy.assertIsFile()
-    }
-
-    @Test
-    public void reportsFailedPublishToHttpRepository() {
-        server.start()
-        def repositoryUrl = "http://localhost:${server.port}"
-        server.addBroken("/")
-
-        dist.testFile("build.gradle") << """
-apply plugin: 'java'
-uploadArchives {
-    repositories {
-        ivy {
-            url "${repositoryUrl}"
-        }
-    }
-}
-"""
-
-        def result = executer.withTasks("uploadArchives").runWithFailure()
-        result.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        result.assertHasCause('Could not publish configuration \':archives\'.')
-        result.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
-
-        server.stop()
-
-        result = executer.withTasks("uploadArchives").runWithFailure()
-        result.assertHasDescription('Execution failed for task \':uploadArchives\'.')
-        result.assertHasCause('Could not publish configuration \':archives\'.')
-        result.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
-    }
-
-    @Test
-    public void usesFirstConfiguredPatternForPublication() {
-        server.start()
-
-        dist.testFile("settings.gradle").text = 'rootProject.name = "publish"'
-        dist.testFile("build.gradle") << """
-    apply plugin: 'java'
-    version = '2'
-    group = 'org.gradle'
-    uploadArchives {
-        repositories {
-            ivy {
-                artifactPattern "http://localhost:${server.port}/primary/[module]/[artifact]-[revision].[ext]"
-                artifactPattern "http://localhost:${server.port}/alternative/[module]/[artifact]-[revision].[ext]"
-                ivyPattern "http://localhost:${server.port}/primary-ivy/[module]/ivy-[revision].xml"
-                ivyPattern "http://localhost:${server.port}/secondary-ivy/[module]/ivy-[revision].xml"
-            }
-        }
-    }
-    """
-        def uploadedJar = dist.testFile('uploaded.jar')
-        def uploadedIvy = dist.testFile('uploaded.xml')
-        server.expectPut('/primary/publish/publish-2.jar', uploadedJar, HttpStatus.ORDINAL_200_OK)
-        server.expectPut('/primary-ivy/publish/ivy-2.xml', uploadedIvy, HttpStatus.ORDINAL_201_Created)
-
-        executer.withTasks("uploadArchives").run()
-
-        uploadedJar.assertIsCopyOf(dist.testFile('build/libs/publish-2.jar'))
-        uploadedIvy.assertIsFile()
-    }
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..cc5f52a
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.IvyRepository
+
+class IvySingleProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+    def "publish multiple artifacts in single configuration"() {
+        settingsFile << "rootProject.name = 'publishTest'"
+        file("file1") << "some content"
+        file("file2") << "other content"
+
+        buildFile << """
+apply plugin: "base"
+
+group = "org.gradle.test"
+version = 1.9
+
+configurations { publish }
+
+task jar1(type: Jar) {
+    baseName = "jar1"
+    from "file1"
+}
+
+task jar2(type: Jar) {
+    baseName = "jar2"
+    from "file2"
+}
+
+artifacts {
+    publish jar1, jar2
+}
+
+uploadPublish {
+    repositories {
+        ivy {
+            url "ivy-repo"
+        }
+    }
+}
+        """
+
+        when:
+        run "uploadPublish"
+
+        then:
+        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "jar1-1.9.jar", "jar2-1.9.jar")
+        ivyModule.moduleDir.file("jar1-1.9.jar").bytes == file("build/libs/jar1-1.9.jar").bytes
+        ivyModule.moduleDir.file("jar2-1.9.jar").bytes == file("build/libs/jar2-1.9.jar").bytes
+
+        and:
+        def ivyDescriptor = ivyModule.ivy
+        ivyDescriptor.expectArtifact("jar1").conf == ["archives", "publish"]
+        ivyDescriptor.expectArtifact("jar2").conf == ["publish"]
+    }
+
+    def "publish multiple artifacts in separate configurations"() {
+        file("settings.gradle") << "rootProject.name = 'publishTest'"
+        file("file1") << "some content"
+        file("file2") << "other content"
+
+        buildFile << """
+apply plugin: "base"
+
+group = "org.gradle.test"
+version = 1.9
+
+configurations { publish1; publish2 }
+
+task jar1(type: Jar) {
+    baseName = "jar1"
+    from "file1"
+}
+
+task jar2(type: Jar) {
+    baseName = "jar2"
+    from "file2"
+}
+
+artifacts {
+    publish1 jar1
+    publish2 jar2
+}
+
+tasks.withType(Upload) {
+    repositories {
+        ivy {
+            url "ivy-repo"
+        }
+    }
+}
+        """
+
+        when:
+        run "uploadPublish$n"
+
+        then:
+        def ivyModule = new IvyRepository(file("ivy-repo")).module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "jar$n-1.9.jar")
+        ivyModule.moduleDir.file("jar$n-1.9.jar").bytes == file("build/libs/jar$n-1.9.jar").bytes
+
+        and:
+        def ivyDescriptor = ivyModule.ivy
+        ivyDescriptor.expectArtifact("jar$n").conf.contains("publish$n" as String)
+        ivyDescriptor.expectArtifact("jar$n").conf.contains("archives") == onArchivesConfig
+
+        where:
+        n | onArchivesConfig
+        1 | true
+        2 | false
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
new file mode 100644
index 0000000..f82c0a9
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenRepository
+import spock.lang.Issue
+
+class MavenMultiProjectPublishIntegrationTest extends AbstractIntegrationSpec {
+    def mavenRepo = new MavenRepository(file("maven-repo"))
+    def mavenModule = mavenRepo.module("org.gradle.test", "project1", "1.9")
+
+    def "project dependency correctly reflected in POM if publication coordinates are unchanged"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+    }
+}
+        """)
+
+        when:
+        run ":project1:uploadArchives"
+
+        then:
+        def pom = mavenModule.pom
+        pom.scopes.compile.assertDependsOn("org.gradle.test", "project2", "1.9")
+    }
+
+    @Issue("GRADLE-443")
+    def "project dependency correctly reflected in POM if archivesBaseName is changed"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+    }
+}
+
+project(":project2") {
+    archivesBaseName = "changed"
+}
+        """)
+
+        when:
+        run ":project1:uploadArchives"
+
+        then:
+        def pom = mavenModule.pom
+        pom.scopes.compile.assertDependsOn("org.gradle.test", "changed", "1.9")
+    }
+
+    @Issue("GRADLE-443")
+    def "project dependency correctly reflected in POM if mavenDeployer.pom.artifactId is changed"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+    }
+}
+
+project(":project2") {
+    uploadArchives {
+        repositories.mavenDeployer {
+            pom.artifactId = "changed"
+        }
+    }
+}
+        """)
+
+        when:
+        run ":project1:uploadArchives"
+
+        then:
+        def pom = mavenModule.pom
+        pom.scopes.compile.assertDependsOn("org.gradle.test", "changed", "1.9")
+    }
+
+    def "project dependency correctly reflected in POM if second artifact is published which differs in classifier"() {
+        createBuildScripts("""
+project(":project1") {
+    dependencies {
+        compile project(":project2")
+    }
+}
+
+project(":project2") {
+    task jar2(type: Jar) {
+        classifier = "other"
+    }
+
+    artifacts {
+        archives jar2
+    }
+}
+        """)
+
+        when:
+        run ":project1:uploadArchives"
+
+        then:
+        def pom = mavenModule.pom
+        pom.scopes.compile.assertDependsOn("org.gradle.test", "project2", "1.9")
+    }
+
+    private void createBuildScripts(String append = "") {
+        settingsFile << """
+include "project1", "project2"
+        """
+
+        buildFile << """
+allprojects {
+    group = "org.gradle.test"
+    version = 1.9
+}
+
+subprojects {
+    apply plugin: "java"
+    apply plugin: "maven"
+
+    uploadArchives {
+        repositories {
+            mavenDeployer {
+                repository(url: "file:///\$rootProject.projectDir/maven-repo")
+            }
+        }
+    }
+}
+
+$append
+        """
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
new file mode 100644
index 0000000..0e61b1a
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPomGenerationIntegrationTest.groovy
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenRepository
+
+// this spec documents the status quo, not a desired behavior
+class MavenPomGenerationIntegrationTest extends AbstractIntegrationSpec {
+    def "how configuration of archive task affects generated POM"() {
+        buildFile << """
+apply plugin: "java"
+apply plugin: "maven"
+
+group = "org.gradle.test"
+version = 1.9
+
+jar {
+    ${jarBaseName ? "baseName = '$jarBaseName'" : ""}
+    ${jarVersion ? "version = '$jarVersion'" : ""}
+    ${jarExtension ? "extension = '$jarExtension'" : ""}
+    ${jarClassifier ? "classifier = '$jarClassifier'" : ""}
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "file:///\$rootProject.projectDir/maven-repo")
+        }
+    }
+}
+        """
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def mavenRepo = new MavenRepository(file("maven-repo"))
+        def mavenModule = mavenRepo.module("org.gradle.test", pomArtifactId, pomVersion)
+        def pom = mavenModule.pom
+        pom.groupId == "org.gradle.test"
+        pom.artifactId == pomArtifactId
+        pom.version == pomVersion
+        pom.packaging == pomPackaging
+
+        where:
+        jarBaseName  | jarVersion | jarExtension | jarClassifier | pomArtifactId | pomVersion | pomPackaging
+        "myBaseName" | "2.3"      | "jar"        | null          | "myBaseName"  | "1.9"      | null
+        "myBaseName" | "2.3"      | "war"        | null          | "myBaseName"  | "1.9"      | "war"
+    }
+
+    def "how configuration of mavenDeployer.pom object affects generated POM"() {
+        buildFile << """
+apply plugin: "java"
+apply plugin: "maven"
+
+group = "org.gradle.test"
+version = 1.9
+
+jar {
+    baseName = "jarBaseName"
+    version = "2.3"
+    extension = "war"
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "file:///\$rootProject.projectDir/maven-repo")
+            ${deployerGroupId ? "pom.groupId = '$deployerGroupId'" : ""}
+            ${deployerArtifactId ? "pom.artifactId = '$deployerArtifactId'" : ""}
+            ${deployerVersion ? "pom.version = '$deployerVersion'" : ""}
+            ${deployerPackaging ? "pom.packaging = '$deployerPackaging'" : ""}
+        }
+    }
+}
+        """
+
+        when:
+        run "uploadArchives"
+
+        then:
+        def mavenRepo = new MavenRepository(file("maven-repo"))
+        def mavenModule = mavenRepo.module(pomGroupId, pomArtifactId, pomVersion)
+        def pom = mavenModule.pom
+        pom.groupId == pomGroupId
+        pom.artifactId == pomArtifactId
+        pom.version == pomVersion
+        pom.packaging == pomPackaging
+
+        where:
+        deployerGroupId  | deployerArtifactId   | deployerVersion | deployerPackaging | pomGroupId       | pomArtifactId        | pomVersion | pomPackaging
+        "deployer.group" | "deployerArtifactId" | "2.7"           | "jar"             | "deployer.group" | "deployerArtifactId" | "2.7"      | "war"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest.groovy
deleted file mode 100644
index d4f5893..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.publish.maven
-
-import org.gradle.integtests.fixtures.TestResources
-import org.junit.Rule
-import org.gradle.integtests.fixtures.GradleDistributionExecuter
-import org.gradle.integtests.fixtures.GradleDistribution
-import org.gradle.integtests.fixtures.MavenRepository
-import org.junit.Test
-
-class MavenPublicationIntegrationTest {
-    @Rule public final GradleDistribution dist = new GradleDistribution()
-    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
-    @Rule public final TestResources testResources = new TestResources()
-
-    @Test
-    public void canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration() {
-        executer.withTasks('uploadArchives').run()
-        def module = repo().module('group', 'root', 1.0)
-        module.assertArtifactsPublished('root-1.0.jar', 'root-1.0.pom')
-    }
-
-    @Test
-    public void canPublishAProjectWithNoMainArtifact() {
-        executer.withTasks('uploadArchives').run()
-        def module = repo().module('group', 'root', 1.0)
-        module.assertArtifactsPublished('root-1.0-source.jar')
-    }
-
-    @Test
-    public void canPublishAProjectWithMetadataArtifacts() {
-        executer.withTasks('uploadArchives').run()
-        def module = repo().module('group', 'root', 1.0)
-        module.assertArtifactsPublished('root-1.0.jar', 'root-1.0.jar.sig', 'root-1.0.pom', 'root-1.0.pom.sig')
-    }
-
-    @Test
-    public void canPublishASnapshotVersion() {
-        dist.testFile('build.gradle') << """
-apply plugin: 'java'
-apply plugin: 'maven'
-
-group = 'org.gradle'
-version = '1.0-SNAPSHOT'
-archivesBaseName = 'test'
-
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            snapshotRepository(url: uri("mavenRepo"))
-        }
-    }
-}
-"""
-
-        executer.withTasks('uploadArchives').run()
-
-        def module = repo().module('org.gradle', 'test', '1.0-SNAPSHOT')
-        module.assertArtifactsPublished('test-1.0-SNAPSHOT.jar', 'test-1.0-SNAPSHOT.pom')
-    }
-
-    def MavenRepository repo() {
-        new MavenRepository(dist.testFile('mavenRepo'))
-    }
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
new file mode 100644
index 0000000..37fb4ea
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.fixtures.MavenRepository
+import org.junit.Rule
+import spock.lang.Unroll
+
+class MavenPublishIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final HttpServer server = new HttpServer()
+
+    def "can publish a project with dependency in mapped and unmapped configuration"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'group'
+version = '1.0'
+repositories { mavenCentral() }
+configurations { custom }
+dependencies {
+    custom 'commons-collections:commons-collections:3.2'
+    runtime 'commons-collections:commons-collections:3.2'
+}
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("mavenRepo"))
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsPublished('root-1.0.jar', 'root-1.0.pom')
+    }
+
+    def "can publish a project with no main artifact"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'base'
+apply plugin: 'maven'
+
+group = 'group'
+version = 1.0
+
+task sourceJar(type: Jar) {
+    classifier = 'source'
+}
+artifacts {
+    archives sourceJar
+}
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri('mavenRepo'))
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsPublished('root-1.0-source.jar')
+    }
+
+    def "can publish a project with metadata artifacts"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'group'
+version = 1.0
+
+task signature {
+    ext.destFile = file("\$buildDir/signature.sig")
+    doLast {
+        destFile.text = 'signature'
+    }
+}
+
+import org.gradle.api.artifacts.maven.MavenDeployment
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+
+artifacts {
+    archives new DefaultPublishArtifact(jar.baseName, "jar.sig", "jar.sig", null, new Date(), signature.destFile, signature)
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: uri("mavenRepo"))
+            beforeDeployment { MavenDeployment deployment ->
+                assert deployment.pomArtifact.file.isFile()
+                assert deployment.pomArtifact.name == 'root'
+                assert deployment.mainArtifact.file == jar.archivePath
+                assert deployment.mainArtifact.name == 'root'
+                assert deployment.artifacts.size() == 3
+                assert deployment.artifacts.contains(deployment.pomArtifact)
+                assert deployment.artifacts.contains(deployment.mainArtifact)
+
+                def pomSignature = file("\${buildDir}/pom.sig")
+                pomSignature.text = 'signature'
+                deployment.addArtifact new DefaultPublishArtifact(deployment.pomArtifact.name, "pom.sig", "pom.sig", null, new Date(), pomSignature)
+            }
+        }
+    }
+}
+"""
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        def module = repo().module('group', 'root', 1.0)
+        module.assertArtifactsPublished('root-1.0.jar', 'root-1.0.jar.sig', 'root-1.0.pom', 'root-1.0.pom.sig')
+    }
+
+    def "can publish a snapshot version"() {
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+
+group = 'org.gradle'
+version = '1.0-SNAPSHOT'
+archivesBaseName = 'test'
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            snapshotRepository(url: uri("mavenRepo"))
+        }
+    }
+}
+"""
+
+        when:
+        succeeds 'uploadArchives'
+
+        then:
+        def module = repo().module('org.gradle', 'test', '1.0-SNAPSHOT')
+        module.assertArtifactsPublished('test-1.0-SNAPSHOT.jar', 'test-1.0-SNAPSHOT.pom')
+    }
+
+    def "can publish multiple deployments with attached artifacts"() {
+        given:
+        server.start()
+
+        settingsFile << "rootProject.name = 'someCoolProject'"
+        buildFile << """
+apply plugin:'java'
+apply plugin:'maven'
+
+version = 1.0
+group =  "org.test"
+
+task sourcesJar(type: Jar) {
+        from sourceSets.main.allSource
+        classifier = 'sources'
+}
+
+task testJar(type: Jar) {
+        baseName = project.name + '-tests'
+}
+
+task testSourcesJar(type: Jar) {
+        baseName = project.name + '-tests'
+        classifier = 'sources'
+}
+
+artifacts { archives sourcesJar, testJar, testSourcesJar }
+
+uploadArchives {
+    repositories{
+        mavenDeployer {
+            repository(url: "http://localhost:${server.port}/repo") {
+               authentication(userName: "testuser", password: "secret")
+            }
+            addFilter('main') {artifact, file ->
+                !artifact.name.endsWith("-tests")
+            }
+            addFilter('tests') {artifact, file ->
+                artifact.name.endsWith("-tests")
+            }
+        }
+    }
+}
+"""
+        when:
+        def module = repo().module('org.test', 'someCoolProject')
+        def moduleDir = module.moduleDir
+        moduleDir.mkdirs()
+
+        and:
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0.pom")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0.jar")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject/1.0", "someCoolProject-1.0-sources.jar")
+        server.expectGetMissing("/repo/org/test/someCoolProject/maven-metadata.xml")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject", "maven-metadata.xml")
+
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0.pom")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0.jar")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests/1.0", "someCoolProject-tests-1.0-sources.jar")
+        server.expectGetMissing("/repo/org/test/someCoolProject-tests/maven-metadata.xml")
+        expectPublishArtifact(moduleDir, "/repo/org/test/someCoolProject-tests", "maven-metadata.xml")
+
+        then:
+        succeeds 'uploadArchives'
+    }
+    
+    def "can publish to an unauthenticated HTTP repository"() {
+        given:
+        server.start()
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'org.test'
+version = '1.0'
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "http://localhost:${server.port}/repo")
+        }
+    }
+}
+"""
+        when:
+        def module = repo().module('org.test', 'root')
+        def moduleDir = module.moduleDir
+        moduleDir.mkdirs()
+        expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.pom")
+        expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.jar")
+        server.expectGetMissing("/repo/org/test/root/maven-metadata.xml")
+        expectPublishArtifact(moduleDir, "/repo/org/test/root", "maven-metadata.xml")
+
+        then:
+        succeeds 'uploadArchives'
+
+        and:
+        module.assertArtifactsPublished('root-1.0.pom', 'root-1.0.jar', 'maven-metadata.xml')
+    }
+
+    private def expectPublishArtifact(def moduleDir, def path, def name) {
+        server.expectPut("$path/$name", moduleDir.file("$name"))
+        server.expectPut("$path/${name}.md5", moduleDir.file("${name}.md5"))
+        server.expectPut("$path/${name}.sha1", moduleDir.file("${name}.sha1"))
+    }
+
+    @Unroll
+    def "can publish to an authenticated HTTP repository using #authScheme auth"() {
+        given:
+        def username = 'testuser'
+        def password = 'password'
+        server.start()
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven'
+group = 'org.test'
+version = '1.0'
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "http://localhost:${server.port}/repo") {
+               authentication(userName: "${username}", password: "${password}")
+            }
+        }
+    }
+}
+"""
+        when:
+        server.authenticationScheme = authScheme
+
+        and:
+        def module = repo().module('org.test', 'root')
+        def moduleDir = module.moduleDir
+        moduleDir.mkdirs()
+        expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.jar", username, password)
+        expectPublishArtifact(moduleDir, "/repo/org/test/root/1.0", "root-1.0.pom", username, password)
+        server.expectGetMissing("/repo/org/test/root/maven-metadata.xml")
+        expectPublishArtifact(moduleDir, "/repo/org/test/root", "maven-metadata.xml", username, password)
+
+        then:
+        succeeds 'uploadArchives'
+
+        and:
+        module.assertArtifactsPublished('root-1.0.pom', 'root-1.0.jar', 'maven-metadata.xml')
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+        // TODO: Does not work with DIGEST authentication
+    }
+
+    private def expectPublishArtifact(def moduleDir, def path, def name, def username, def password) {
+        server.expectPut("$path/$name", username, password, moduleDir.file("$name"))
+        server.expectPut("$path/${name}.md5", username, password, moduleDir.file("${name}.md5"))
+        server.expectPut("$path/${name}.sha1", username, password, moduleDir.file("${name}.sha1"))
+    }
+
+    def MavenRepository repo() {
+        new MavenRepository(distribution.testFile('mavenRepo'))
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy
index 00134fd..dfaac85 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/AbstractDependencyResolutionTest.groovy
@@ -36,8 +36,4 @@ abstract class AbstractDependencyResolutionTest extends AbstractIntegrationSpec
     MavenRepository mavenRepo() {
         return new MavenRepository(file('repo'))
     }
-
-    def cleanup() {
-        server.resetExpectations()
-    }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
index bd6c5b2..60db2a9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
@@ -209,7 +209,9 @@ task test << {
 }
 """
 
-        inTestDirectory().withTasks('test').run()
+        executer.withDeprecationChecksDisabled()
+        def result = inTestDirectory().withTasks('test').run()
+        assert result.output.contains('relying on packaging to define the extension of the main artifact is deprecated')
     }
 
     @Test
@@ -347,7 +349,9 @@ task test << {
     checkDeps configurations.extendedWithType, ['external1-1.0.zip', 'external1-1.0-baseClassifier.jar', 'external1-1.0.txt']
 }
 """
-        inTestDirectory().withTasks('test').run()
+        executer.withDeprecationChecksDisabled()
+        def result = inTestDirectory().withTasks('test').run()
+        assert result.output.contains('relying on packaging to define the extension of the main artifact is deprecated')
     }
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
index 7924f3e..88e76fd 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
@@ -92,8 +92,4 @@ task retrieve(type: Sync) {
     MavenRepository repo() {
         return new MavenRepository(file('repo'))
     }
-
-    def cleanup() {
-        server.resetExpectations()
-    }
 }
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index f04c5c4..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve;
-
-
-public class CacheDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-
-    public void "cache handles manual deletion of cached artifacts"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        def cacheDir = distribution.userHomeDir.file('caches').toURI()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-task deleteCacheFiles(type: Delete) {
-    delete fileTree(dir: '${cacheDir}', includes: ['**/projectA/**'])
-}
-"""
-
-        and:
-        server.allowGet("/repo", repo.rootDir)
-
-        and:
-        succeeds('listJars')
-        succeeds('deleteCacheFiles')
-        
-        when:
-        server.resetExpectations()
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-    }
-
-    public void "cache entries are segregated between different repositories"() {
-        server.start()
-        given:
-        def repo1 = ivyRepo('ivy-repo-a')
-        def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
-        def repo2 = ivyRepo('ivy-repo-b')
-        def module2 = repo2.module('org.gradle', 'testproject', '1.0').publish()
-        module2.jarFile << "Some extra content"
-
-        and:
-        settingsFile << "include 'a','b'"
-        buildFile << """
-subprojects {
-    configurations {
-        test
-    }
-    dependencies {
-        test "org.gradle:testproject:1.0"
-    }
-    task retrieve(type: Sync) {
-        into 'build'
-        from configurations.test
-    }
-}
-project('a') {
-    repositories {
-        ivy { url "http://localhost:${server.port}/repo-a" }
-    }
-}
-project('b') {
-    repositories {
-        ivy { url "http://localhost:${server.port}/repo-b" }
-    }
-}
-"""
-
-        when:
-        server.expectGet('/repo-a/org.gradle/testproject/1.0/ivy-1.0.xml', module1.ivyFile)
-        server.expectGet('/repo-a/org.gradle/testproject/1.0/testproject-1.0.jar', module1.jarFile)
-
-        module2.expectIvyHead(server, "/repo-b")
-        server.expectGet('/repo-b/org.gradle/testproject/1.0/ivy-1.0.xml.sha1', module2.sha1File(module2.ivyFile))
-        server.expectGet('/repo-b/org.gradle/testproject/1.0/ivy-1.0.xml', module2.ivyFile)
-        module2.expectArtifactHead(server, "/repo-b")
-        server.expectGet('/repo-b/org.gradle/testproject/1.0/testproject-1.0.jar.sha1', module2.sha1File(module2.jarFile))
-        server.expectGet('/repo-b/org.gradle/testproject/1.0/testproject-1.0.jar', module2.jarFile)
-
-        then:
-        succeeds 'retrieve'
-
-        and:
-        file('a/build/testproject-1.0.jar').assertIsCopyOf(module1.jarFile)
-        file('b/build/testproject-1.0.jar').assertIsCopyOf(module2.jarFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
new file mode 100644
index 0000000..a7aa1a2
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve;
+
+
+public class CacheResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    public void "cache handles manual deletion of cached artifacts"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        def cacheDir = distribution.userHomeDir.file('caches').toURI()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+task deleteCacheFiles(type: Delete) {
+    delete fileTree(dir: '${cacheDir}', includes: ['**/projectA/**'])
+}
+"""
+
+        and:
+        server.allowGet("/repo", repo.rootDir)
+
+        and:
+        succeeds('listJars')
+        succeeds('deleteCacheFiles')
+        
+        when:
+        server.resetExpectations()
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "cache entries are segregated between different repositories"() {
+        server.start()
+        given:
+        def repo1 = ivyRepo('ivy-repo-a')
+        def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
+        def repo2 = ivyRepo('ivy-repo-b')
+        def module2 = repo2.module('org.gradle', 'testproject', '1.0').publishWithChangedContent()
+
+        and:
+        settingsFile << "include 'a','b'"
+        buildFile << """
+subprojects {
+    configurations {
+        test
+    }
+    dependencies {
+        test "org.gradle:testproject:1.0"
+    }
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.test
+    }
+}
+project('a') {
+    repositories {
+        ivy { url "http://localhost:${server.port}/repo-a" }
+    }
+}
+project('b') {
+    repositories {
+        ivy { url "http://localhost:${server.port}/repo-b" }
+    }
+}
+"""
+
+        when:
+        server.expectGet('/repo-a/org.gradle/testproject/1.0/ivy-1.0.xml', module1.ivyFile)
+        server.expectGet('/repo-a/org.gradle/testproject/1.0/testproject-1.0.jar', module1.jarFile)
+
+        module2.expectIvyHead(server, "/repo-b")
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/ivy-1.0.xml.sha1', module2.sha1File(module2.ivyFile))
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/ivy-1.0.xml', module2.ivyFile)
+        module2.expectArtifactHead(server, "/repo-b")
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/testproject-1.0.jar.sha1', module2.sha1File(module2.jarFile))
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/testproject-1.0.jar', module2.jarFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('a/build/testproject-1.0.jar').assertIsCopyOf(module1.jarFile)
+        file('b/build/testproject-1.0.jar').assertIsCopyOf(module2.jarFile)
+    }
+
+    public void "reuses a cached artifact retrieved from a different repository when sha1 matches"() {
+        server.start()
+        given:
+        def repo1 = ivyRepo('ivy-repo-a')
+        def module1 = repo1.module('org.gradle', 'testproject', '1.0').publish()
+        def repo2 = ivyRepo('ivy-repo-b')
+        def module2 = repo2.module('org.gradle', 'testproject', '1.0').publish()
+
+        and:
+        settingsFile << "include 'a','b'"
+        buildFile << """
+subprojects {
+    configurations {
+        test
+    }
+    dependencies {
+        test "org.gradle:testproject:1.0"
+    }
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.test
+    }
+}
+project('a') {
+    repositories {
+        ivy { url "http://localhost:${server.port}/repo-a" }
+    }
+}
+project('b') {
+    repositories {
+        ivy { url "http://localhost:${server.port}/repo-b" }
+    }
+}
+"""
+
+        when:
+        server.expectGet('/repo-a/org.gradle/testproject/1.0/ivy-1.0.xml', module1.ivyFile)
+        server.expectGet('/repo-a/org.gradle/testproject/1.0/testproject-1.0.jar', module1.jarFile)
+
+        module2.expectIvyHead(server, "/repo-b")
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/ivy-1.0.xml.sha1', module2.sha1File(module2.ivyFile))
+        module2.expectArtifactHead(server, "/repo-b")
+        server.expectGet('/repo-b/org.gradle/testproject/1.0/testproject-1.0.jar.sha1', module2.sha1File(module2.jarFile))
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('a/build/testproject-1.0.jar').assertIsCopyOf(module1.jarFile)
+        file('b/build/testproject-1.0.jar').assertIsCopyOf(module2.jarFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpEncodingDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpEncodingDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index d0fd694..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpEncodingDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve;
-
-
-public class HttpEncodingDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    public void "handles gzip encoded content"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        server.expectGetGZipped('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGetGZipped('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpProxyDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpProxyDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index c1eeb50..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpProxyDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve
-
-import org.gradle.integtests.fixtures.TestProxyServer
-import org.gradle.util.SetSystemProperties
-import org.junit.Rule
-
-class HttpProxyDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
-    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
-
-    public void "uses configured proxy to access remote HTTP repository"() {
-        server.start()
-        proxyServer.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://not.a.real.domain/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}")
-
-        and:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-
-        and:
-        proxyServer.requestCount == 2
-    }
-
-    public void "uses authenticated proxy to access remote HTTP repository"() {
-        server.start()
-        proxyServer.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://not.a.real.domain/repo"
-    }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.nonProxyHosts=foo",
-                               "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
-
-        and:
-        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
-
-        and:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-
-        and:
-        proxyServer.requestCount == 2
-    }
-
-    public void "passes target credentials to target server via proxy"() {
-        server.start()
-        proxyServer.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://not.a.real.domain/repo"
-        credentials {
-            username 'targetUser'
-            password 'targetPassword'
-        }
-    }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
-
-        and:
-        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
-
-        and:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'targetUser', 'targetPassword', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'targetUser', 'targetPassword', module.jarFile)
-
-        then:
-        executer.withDeprecationChecksDisabled()
-        succeeds('listJars')
-
-        and:
-        proxyServer.requestCount == 2
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpRedirectDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpRedirectDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index 9d0c8a5..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/HttpRedirectDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.integtests.resolve
-
-import org.gradle.util.SetSystemProperties
-import org.junit.Rule
-import spock.lang.Issue
-
-class HttpRedirectDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
-
-    public void "resolves module artifacts via HTTP redirect"() {
-        server.start()
-
-        given:
-        def module = ivyRepo().module('group', 'projectA').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.0' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-}
-"""
-
-        when:
-        server.expectGetRedirected('/repo/group/projectA/1.0/ivy-1.0.xml', '/redirected/group/projectA/1.0/ivy-1.0.xml')
-        server.expectGet('/redirected/group/projectA/1.0/ivy-1.0.xml', module.ivyFile)
-        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.jar', '/redirected/group/projectA/1.0/projectA-1.0.jar')
-        server.expectGet('/redirected/group/projectA/1.0/projectA-1.0.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-    }
-
-    @Issue('GRADLE-2196')
-    public void "resolves artifact-only module via HTTP redirect"() {
-        server.start()
-
-        given:
-        def module = ivyRepo().module('group', 'projectA').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.0 at zip' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.zip']
-}
-"""
-
-        when:
-        server.expectGetMissing('/repo/group/projectA/1.0/ivy-1.0.xml')
-        server.expectHeadRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', '/redirected/group/projectA/1.0/projectA-1.0.zip')
-        server.expectHead('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
-        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', '/redirected/group/projectA/1.0/projectA-1.0.zip')
-        server.expectGet('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
-
-        then:
-        succeeds('listJars')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
index 4374671..7ba320a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ResolvedConfigurationIntegrationTest.groovy
@@ -19,8 +19,8 @@ import org.gradle.api.Project
 import org.gradle.api.artifacts.LenientConfiguration
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.specs.Specs
-import org.gradle.integtests.fixtures.MavenRepository
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
+import org.gradle.integtests.fixtures.MavenRepository
 import org.gradle.util.HelperUtil
 import org.junit.Before
 import org.junit.Test
@@ -47,7 +47,7 @@ public class ResolvedConfigurationIntegrationTest extends AbstractIntegrationTes
 
         project.dependencies {
             compile 'org.foo:hiphop:1.0'
-            compile 'org.foo:hiphopxx:1.0' //does not exist
+            compile 'unresolved.org:hiphopxx:3.0' //does not exist
             compile childProject
 
             compile 'org.foo:rock:1.0' //contains unresolved transitive dependency
@@ -64,8 +64,8 @@ public class ResolvedConfigurationIntegrationTest extends AbstractIntegrationTes
         assert resolved.find { it.moduleName == 'child' }
 
         assert unresolved.size() == 2
-        assert unresolved.find { it.id.contains 'hiphopxx' }
-        assert unresolved.find { it.id.contains 'some unresolved dependency' }
+        assert unresolved.find { it.selector.group == 'unresolved.org' && it.selector.name == 'hiphopxx' && it.selector.version == '3.0' }
+        assert unresolved.find { it.selector.name == 'some unresolved dependency' }
     }
 
     public MavenRepository maven(File repo) {
@@ -108,6 +108,6 @@ public class ResolvedConfigurationIntegrationTest extends AbstractIntegrationTes
 
         assert resolved.size() == 0
         assert unresolved.size() == 1
-        assert unresolved.find { it.id.contains 'hiphopxx' }
+        assert unresolved.find { it.selector.name == 'hiphopxx' }
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
index 45e76be..6fc6cf8 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/CacheReuseCrossVersionIntegrationTest.groovy
@@ -60,10 +60,10 @@ task retrieve(type: Sync) {
 
         when:
         server.resetExpectations()
-        projectB.expectPomHead(server)
-        server.expectGet("/org/name/projectB/1.0/projectB-1.0.pom.sha1", projectB.sha1File(projectB.pomFile))
-        projectB.expectArtifactHead(server)
-        server.expectGet("/org/name/projectB/1.0/projectB-1.0.jar.sha1", projectB.sha1File(projectB.artifactFile))
+        projectB.allowPomHead(server)
+        projectB.allowPomSha1Get(server)
+        projectB.allowArtifactHead(server)
+        projectB.allowArtifactSha1Get(server)
 
         and:
         version current withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
@@ -107,8 +107,8 @@ task retrieve(type: Sync) {
 
         when:
         server.resetExpectations()
-        projectB.expectPomHead(server)
-        projectB.expectArtifactHead(server)
+        projectB.allowPomHead(server)
+        projectB.allowArtifactHead(server)
 
         and:
         version current withUserHomeDir userHome withTasks 'retrieve' withArguments '-i' run()
@@ -117,9 +117,4 @@ task retrieve(type: Sync) {
         file('libs').assertHasDescendants('projectB-1.0.jar')
         file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
     }
-
-    def cleanup() {
-        server.resetExpectations()
-    }
-
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
index 47f6285..7900dc6 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
@@ -71,9 +71,4 @@ task retrieve(type: Sync) {
         file('libs').assertHasDescendants('projectB-1.0.jar')
         file('libs/projectB-1.0.jar').assertContentsHaveNotChangedSince(snapshot)
     }
-
-    def cleanup() {
-        server.resetExpectations()
-    }
-
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenLocalCacheReuseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenLocalCacheReuseIntegrationTest.groovy
deleted file mode 100644
index 12d9058..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenLocalCacheReuseIntegrationTest.groovy
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.artifactreuse
-
-import org.gradle.integtests.fixtures.HttpServer
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.util.TestFile
-import org.junit.Rule
-
-class MavenLocalCacheReuseIntegrationTest extends AbstractIntegrationSpec {
-    @Rule public final HttpServer server = new HttpServer()
-    TestFile repoFile
-    
-    def "setup"() {
-        requireOwnUserHomeDir()
-        repoFile = file('repo')
-    }
-
-    def "uses cached artifacts from maven local cache"() {
-        given:
-        publishAndInstallToMaven()
-        server.start()
-
-        buildFile.text = """
-repositories {
-    maven { url "http://localhost:${server.port}" }
-}
-configurations { compile }
-dependencies {
-    compile 'gradletest.maven.local.cache.test:foo:1.0'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'build'
-}
-"""
-
-        when:
-        server.expectHead('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom'))
-        server.expectGet('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom.sha1', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom.sha1'))
-        server.expectHead('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar'))
-        server.expectGet('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar.sha1', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar.sha1'))
-
-        then:
-        run 'retrieve'
-    }
-
-    private def publishAndInstallToMaven() {
-
-        settingsFile << """
-            rootProject.name = 'foo'
-"""
-
-        buildFile.text = """
-apply plugin: 'java'
-apply plugin: 'maven'
-
-group = 'gradletest.maven.local.cache.test'
-version = '1.0'
-
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: "${repoFile.toURI().toURL()}")
-        }
-    }
-}
-"""
-
-        run 'install'
-        run 'uploadArchives'
-    }
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
new file mode 100644
index 0000000..aedf714
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.artifactreuse
+
+import org.gradle.integtests.fixture.M2Installation
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenM2CacheReuseIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def setup(){
+        requireOwnUserHomeDir();
+    }
+
+    def "uses cached artifacts from maven local cache"() {
+        given:
+        publishAndInstallToMaven()
+        server.start()
+
+        buildFile.text = """
+repositories {
+    maven { url "http://localhost:${server.port}" }
+}
+configurations { compile }
+dependencies {
+    compile 'gradletest.maven.local.cache.test:foo:1.0'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        def repoFile = file('repo')
+        server.expectHead('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom'))
+        server.expectGet('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom.sha1', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.pom.sha1'))
+        server.expectHead('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar'))
+        server.expectGet('/gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar.sha1', repoFile.file('gradletest/maven/local/cache/test/foo/1.0/foo-1.0.jar.sha1'))
+
+        then:
+        executer.withArguments("-Duser.home=${distribution.getUserHomeDir()}")
+        run 'retrieve'
+    }
+
+    private def publishAndInstallToMaven() {
+        def module1 = mavenRepo().module('gradletest.maven.local.cache.test', "foo", "1.0")
+        module1.publish();
+        def module2 = new M2Installation(distribution.getUserHomeDir().file(".m2")).mavenRepo().module('gradletest.maven.local.cache.test', "foo", "1.0")
+        module2.publish()
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..a58575f
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.resolve.http
+
+import org.gradle.integtests.fixtures.HttpServer
+import org.hamcrest.Matchers
+import spock.lang.Unroll
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class HttpAuthenticationDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    static String badCredentials = "credentials{username 'testuser'; password 'bad'}"
+
+    @Unroll
+    public void "can resolve dependencies from #authScheme authenticated HTTP ivy repository"() {
+        server.start()
+        given:
+        def moduleA = ivyRepo().module('group', 'projectA', '1.2').publish()
+        ivyRepo().module('group', 'projectB', '2.1').publish()
+        ivyRepo().module('group', 'projectB', '2.2').publish()
+        def moduleB = ivyRepo().module('group', 'projectB', '2.3').publish()
+        ivyRepo().module('group', 'projectB', '3.0').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}/repo"
+
+        credentials {
+            password 'password'
+            username 'username'
+        }
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+    compile 'group:projectB:2.+'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar','projectB-2.3.jar']
+}
+"""
+
+        when:
+        server.authenticationScheme = authScheme
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', moduleA.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', moduleA.jarFile)
+        server.expectGetDirectoryListing("/repo/group/projectB/", 'username', 'password', moduleB.moduleDir.parentFile)
+        server.expectGet("/repo/group/projectB/2.3/ivy-2.3.xml", 'username', 'password', moduleB.ivyFile)
+        server.expectGet("/repo/group/projectB/2.3/projectB-2.3.jar", 'username', 'password', moduleB.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    public void "can resolve dependencies from #authScheme authenticated HTTP maven repository"() {
+        server.start()
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2').publish()
+        mavenRepo().module('group', 'projectB', '2.0').publish()
+        mavenRepo().module('group', 'projectB', '2.2').publish()
+        def moduleB = mavenRepo().module('group', 'projectB', '2.3').publish()
+        mavenRepo().module('group', 'projectB', '3.0').publish()
+        def moduleC = mavenRepo().module('group', 'projectC', '3.1-SNAPSHOT').publish()
+        def moduleD = mavenRepo().module('group', 'projectD', '4-SNAPSHOT').withNonUniqueSnapshots().publish()
+        and:
+        buildFile << """
+repositories {
+    maven {
+        url "http://localhost:${server.port}/repo"
+
+        credentials {
+            password 'password'
+            username 'username'
+        }
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+    compile 'group:projectB:2.+'
+    compile 'group:projectC:3.1-SNAPSHOT'
+    compile 'group:projectD:4-SNAPSHOT'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar', 'projectB-2.3.jar', 'projectC-3.1-SNAPSHOT.jar', 'projectD-4-SNAPSHOT.jar']
+}
+"""
+
+        when:
+        server.authenticationScheme = authScheme
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', moduleA.pomFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', moduleA.artifactFile)
+        server.expectGet('/repo/group/projectB/maven-metadata.xml', 'username', 'password', moduleB.rootMetaDataFile)
+        server.expectGet('/repo/group/projectB/2.3/projectB-2.3.pom', 'username', 'password', moduleB.pomFile)
+        server.expectGet('/repo/group/projectB/2.3/projectB-2.3.jar', 'username', 'password', moduleB.artifactFile)
+
+        server.expectGet('/repo/group/projectC/3.1-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleC.metaDataFile) // TODO: double check why we have two get requests here.
+        server.expectGet('/repo/group/projectC/3.1-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleC.metaDataFile)
+        server.expectGet("/repo/group/projectC/3.1-SNAPSHOT/projectC-${moduleC.getPublishArtifactVersion()}.pom", 'username', 'password', moduleC.pomFile)
+        server.expectGet("/repo/group/projectC/3.1-SNAPSHOT/projectC-${moduleC.getPublishArtifactVersion()}.jar", 'username', 'password', moduleC.artifactFile)
+
+        server.expectGet('/repo/group/projectD/4-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleD.metaDataFile)  //same here: two requests necessary?
+        server.expectGet('/repo/group/projectD/4-SNAPSHOT/maven-metadata.xml', 'username', 'password', moduleD.metaDataFile)
+        server.expectGet("/repo/group/projectD/4-SNAPSHOT/projectD-4-SNAPSHOT.pom", 'username', 'password', moduleD.pomFile)
+        server.expectGet("/repo/group/projectD/4-SNAPSHOT/projectD-4-SNAPSHOT.jar", 'username', 'password', moduleD.artifactFile)
+
+        then:
+        succeeds('listJars')
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    def "reports failure resolving with #credsName credentials from #authScheme authenticated HTTP ivy repository"() {
+        server.start()
+        given:
+        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
+        when:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+   repositories {
+       ivy {
+            url "http://localhost:${server.port}/repo"
+            $creds
+        }
+   }
+   configurations { compile }
+   dependencies {
+       compile 'group:projectA:1.2'
+   }
+   task listJars << {
+       assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+   }
+   """
+
+        and:
+        server.authenticationScheme = authScheme
+        server.allowGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', module.ivyFile)
+
+        then:
+        fails 'listJars'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':listJars\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST, HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+        credsName << ['empty', 'empty', 'bad', 'bad']
+        creds << ['', '', badCredentials, badCredentials]
+    }
+
+    @Unroll
+    def "reports failure resolving with #credsName credentials from #authScheme authenticated HTTP maven repository"() {
+        given:
+        server.start()
+
+        and:
+        def module = mavenRepo().module('group', 'projectA', '1.2').publish()
+
+        when:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+ repositories {
+     maven {
+         url "http://localhost:${server.port}/repo"
+        $creds
+     }
+ }
+ configurations { compile }
+ dependencies {
+     compile 'group:projectA:1.2'
+ }
+ task listJars << {
+     assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+ }
+ """
+
+        and:
+        server.authenticationScheme = authScheme
+        server.allowGet('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', module.pomFile)
+
+        then:
+        fails 'listJars'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':listJars\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
+        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST, HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+        credsName << ['empty', 'empty', 'bad', 'bad']
+        creds << ['', '', badCredentials, badCredentials]
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy
new file mode 100644
index 0000000..725f6f4
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpEncodingDependencyResolutionIntegrationTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.http
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest;
+
+
+public class HttpEncodingDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "handles gzip encoded content"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        server.expectGetGZipped('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGetGZipped('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..372f922
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpProxyResolveIntegrationTest.groovy
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.http
+
+import org.gradle.integtests.fixtures.TestProxyServer
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Unroll
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class HttpProxyResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule TestProxyServer proxyServer = new TestProxyServer(server)
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+
+    public void "uses configured proxy to access remote HTTP repository"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://not.a.real.domain/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}")
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        and:
+        proxyServer.requestCount == 2
+    }
+
+    public void "uses authenticated proxy to access remote HTTP repository"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://not.a.real.domain/repo"
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.nonProxyHosts=foo",
+                               "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
+
+        and:
+        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        and:
+        proxyServer.requestCount == 2
+    }
+
+    @Unroll
+    public void "passes target credentials to #authScheme authenticated server via proxy"() {
+        server.start()
+        proxyServer.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://not.a.real.domain/repo"
+        credentials {
+            username 'targetUser'
+            password 'targetPassword'
+        }
+    }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        server.authenticationScheme = authScheme
+        executer.withArguments("-Dhttp.proxyHost=localhost", "-Dhttp.proxyPort=${proxyServer.port}", "-Dhttp.proxyUser=proxyUser", "-Dhttp.proxyPassword=proxyPassword")
+
+        and:
+        proxyServer.requireAuthentication('proxyUser', 'proxyPassword')
+
+        and:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'targetUser', 'targetPassword', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'targetUser', 'targetPassword', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        and:
+        // 1 extra request for authentication
+        proxyServer.requestCount == 3
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
new file mode 100644
index 0000000..04ed3d2
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpRedirectResolveIntegrationTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.resolve.http
+
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+import spock.lang.Issue
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.integtests.fixtures.HttpServer
+
+class HttpRedirectResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule SetSystemProperties systemProperties = new SetSystemProperties()
+    @Rule public final HttpServer server2 = new HttpServer()
+
+    public void "resolves module artifacts via HTTP redirect"() {
+        server.start()
+        server2.start()
+
+        given:
+        def module = ivyRepo().module('group', 'projectA').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.0' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.expectGetRedirected('/repo/group/projectA/1.0/ivy-1.0.xml', "http://localhost:${server2.port}/redirected/group/projectA/1.0/ivy-1.0.xml")
+        server2.expectGet('/redirected/group/projectA/1.0/ivy-1.0.xml', module.ivyFile)
+        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.jar', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.jar")
+        server2.expectGet('/redirected/group/projectA/1.0/projectA-1.0.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+
+    @Issue('GRADLE-2196')
+    public void "resolves artifact-only module via HTTP redirect"() {
+        server.start()
+        server2.start()
+
+        given:
+        def module = ivyRepo().module('group', 'projectA').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.0 at zip' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.zip']
+}
+"""
+
+        when:
+        server.expectGetMissing('/repo/group/projectA/1.0/ivy-1.0.xml')
+        server.expectHeadRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.zip")
+        server2.expectHead('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
+        server.expectGetRedirected('/repo/group/projectA/1.0/projectA-1.0.zip', "http://localhost:${server2.port}/redirected/group/projectA/1.0/projectA-1.0.zip")
+        server2.expectGet('/redirected/group/projectA/1.0/projectA-1.0.zip', module.jarFile)
+
+        then:
+        succeeds('listJars')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index 63f04c1..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.ivy
-
-import org.hamcrest.Matchers
-
-import static org.hamcrest.Matchers.containsString
-import static org.hamcrest.Matchers.startsWith
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyBrokenRemoteDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-
-    public void "reports and caches missing module"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-configurations { missing }
-dependencies {
-    missing 'group:projectA:1.2'
-}
-if (project.hasProperty('doNotCacheChangingModules')) {
-    configurations.all {
-        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-    }
-}
-task showMissing << { println configurations.missing.files }
-"""
-
-        when:
-        server.expectGetMissing('/group/projectA/1.2/ivy-1.2.xml')
-        server.expectHeadMissing('/group/projectA/1.2/projectA-1.2.jar')
-        fails("showMissing")
-
-        then:
-        failure.assertHasDescription('Execution failed for task \':showMissing\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':missing\'.')
-        failure.assertThatCause(Matchers.containsString('Could not find group:group, module:projectA, version:1.2.'))
-
-        when:
-        server.resetExpectations() // Missing status is cached
-        fails('showMissing')
-
-        then:
-        failure.assertHasDescription('Execution failed for task \':showMissing\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':missing\'.')
-        failure.assertThatCause(Matchers.containsString('Could not find group:group, module:projectA, version:1.2.'))
-    }
-
-    public void "reports and recovers from broken module"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.3')
-        module.publish()
-
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-configurations { broken }
-dependencies {
-    broken 'group:projectA:1.3'
-}
-task showBroken << { println configurations.broken.files }
-"""
-
-        when:
-        server.addBroken('/')
-        fails("showBroken")
-
-        then:
-        failure.assertHasDescription('Execution failed for task \':showBroken\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':broken\'.')
-        failure.assertHasCause('Could not resolve group:group, module:projectA, version:1.3')
-        failure.assertHasCause("Could not GET 'http://localhost:${server.port}/group/projectA/1.3/ivy-1.3.xml'. Received status code 500 from server: broken")
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/group/projectA/1.3/ivy-1.3.xml', module.ivyFile)
-        server.expectGet('/group/projectA/1.3/projectA-1.3.jar', module.jarFile)
-        
-        then:
-        succeeds("showBroken")
-    }
-
-    public void "reports and caches missing artifacts"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        and:
-        def module = ivyRepo().module('group', 'projectA', '1.2')
-        module.publish()
-
-        when:
-        server.expectGet('/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGetMissing('/group/projectA/1.2/projectA-1.2.jar')
-
-        then:
-        fails "retrieve"
-
-        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
-
-        when:
-        server.resetExpectations()
-
-        then:
-        fails "retrieve"
-        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
-    }
-
-    public void "reports and recovers from failed artifact download"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        and:
-        def module = ivyRepo().module('group', 'projectA', '1.2')
-        module.publish()
-
-        when:
-        server.expectGet('/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.addBroken('/group/projectA/1.2/projectA-1.2.jar')
-
-        then:
-        fails "retrieve"
-        failure.assertHasCause("Could not download artifact 'group:projectA:1.2 at jar'")
-        failure.assertThatCause(startsWith("Could not GET"))
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds "retrieve"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
new file mode 100644
index 0000000..728a768
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.ivy
+
+import org.hamcrest.Matchers
+
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.startsWith
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyBrokenRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    public void "reports and caches missing module"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.2')
+        module.publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { missing }
+dependencies {
+    missing 'group:projectA:1.2'
+}
+if (project.hasProperty('doNotCacheChangingModules')) {
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+}
+task showMissing << { println configurations.missing.files }
+"""
+
+        when:
+        server.expectGetMissing('/group/projectA/1.2/ivy-1.2.xml')
+        server.expectHeadMissing('/group/projectA/1.2/projectA-1.2.jar')
+        fails("showMissing")
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':showMissing\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':missing\'.')
+        failure.assertThatCause(Matchers.containsString('Could not find group:group, module:projectA, version:1.2.'))
+
+        when:
+        server.resetExpectations() // Missing status is cached
+        fails('showMissing')
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':showMissing\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':missing\'.')
+        failure.assertThatCause(Matchers.containsString('Could not find group:group, module:projectA, version:1.2.'))
+    }
+
+    public void "reports and recovers from broken module"() {
+        server.start()
+
+        given:
+        def repo = ivyRepo()
+        def module = repo.module('group', 'projectA', '1.3')
+        module.publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { broken }
+dependencies {
+    broken 'group:projectA:1.3'
+}
+task showBroken << { println configurations.broken.files }
+"""
+
+        when:
+        server.addBroken('/')
+        fails("showBroken")
+
+        then:
+        failure.assertHasDescription('Execution failed for task \':showBroken\'.')
+        failure.assertHasCause('Could not resolve all dependencies for configuration \':broken\'.')
+        failure.assertHasCause('Could not resolve group:group, module:projectA, version:1.3')
+        failure.assertHasCause("Could not GET 'http://localhost:${server.port}/group/projectA/1.3/ivy-1.3.xml'. Received status code 500 from server: broken")
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/group/projectA/1.3/ivy-1.3.xml', module.ivyFile)
+        server.expectGet('/group/projectA/1.3/projectA-1.3.jar', module.jarFile)
+        
+        then:
+        succeeds("showBroken")
+    }
+
+    public void "reports and caches missing artifacts"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = ivyRepo().module('group', 'projectA', '1.2')
+        module.publish()
+
+        when:
+        server.expectGet('/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGetMissing('/group/projectA/1.2/projectA-1.2.jar')
+
+        then:
+        fails "retrieve"
+
+        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
+
+        when:
+        server.resetExpectations()
+
+        then:
+        fails "retrieve"
+        failure.assertThatCause(containsString("Artifact 'group:projectA:1.2 at jar' not found"))
+    }
+
+    public void "reports and recovers from failed artifact download"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        and:
+        def module = ivyRepo().module('group', 'projectA', '1.2')
+        module.publish()
+
+        when:
+        server.expectGet('/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.addBroken('/group/projectA/1.2/projectA-1.2.jar')
+
+        then:
+        fails "retrieve"
+        failure.assertHasCause("Could not download artifact 'group:projectA:1.2 at jar'")
+        failure.assertThatCause(startsWith("Could not GET"))
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds "retrieve"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolutionIntegrationTest.groovy
deleted file mode 100644
index 93228de..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyChangingModuleRemoteResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "detects changed module descriptor when flagged as changing"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-
-configurations { compile }
-
-configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: true
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when: "Version 1.1 is published"
-        def module = ivyRepo().module("group", "projectA", "1.1")
-        module.publish()
-
-        and: "Server handles requests"
-        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
-
-        and: "We request 1.1 (changing)"
-        run 'retrieve'
-
-        then: "Version 1.1 jar is downloaded"
-        file('build').assertHasDescendants('projectA-1.1.jar')
-
-        when: "Module meta-data is changed (new artifact)"
-        module.artifact([name: 'other'])
-        module.dependsOn("group", "projectB", "2.0")
-        module.publish()
-        def moduleB = ivyRepo().module("group", "projectB", "2.0")
-        moduleB.publish();
-
-        and: "Server handles requests"
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml.sha1', module.sha1File(module.ivyFile))
-        server.expectHeadThenGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar.sha1', module.sha1File(module.jarFile))
-        server.expectHeadThenGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
-        server.expectHeadThenGet('/repo/group/projectA/1.1/other-1.1.jar', module.moduleDir.file('other-1.1.jar'))
-        server.expectGet('/repo/group/projectB/2.0/ivy-2.0.xml', moduleB.ivyFile)
-        server.expectGet('/repo/group/projectB/2.0/projectB-2.0.jar', moduleB.jarFile)
-
-        and: "We request 1.1 again"
-        run 'retrieve'
-
-        then: "We get all artifacts, including the new ones"
-        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar', 'projectB-2.0.jar')
-    }
-
-    def "can mark a module as changing after first retrieval"() {
-        server.start()
-
-        given:
-        buildFile << """
-def isChanging = project.hasProperty('isChanging') ? true : false
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-
-configurations { compile }
-configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: isChanging
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-        and:
-        def module = ivyRepo().module("group", "projectA", "1.1")
-        module.publish()
-        server.allowGet('/repo', ivyRepo().rootDir)
-
-        when: 'original retrieve'
-        run 'retrieve'
-
-        then:
-        def jarSnapshot = file('build/projectA-1.1.jar').snapshot()
-
-        when:
-        module.publishWithChangedContent()
-
-        and:
-        executer.withArguments('-PisChanging')
-        run 'retrieve'
-
-        then:
-        file('build/projectA-1.1.jar').assertHasChangedSince(jarSnapshot)
-    }
-
-    def "detects changed artifact when flagged as changing"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-
-configurations { compile }
-
-configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: true
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        and:
-        def module = ivyRepo().module("group", "projectA", "1.1").publish()
-
-        when:
-        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
-
-        run 'retrieve'
-
-        then:
-        def jarFile = file('build/projectA-1.1.jar')
-        jarFile.assertIsCopyOf(module.jarFile)
-        def snapshot = jarFile.snapshot()
-
-        when:
-        module.publishWithChangedContent()
-
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        module.expectIvyHead(server, '/repo')
-        module.expectIvySha1Get(server, '/repo')
-        module.expectIvyGet(server, '/repo')
-        module.expectArtifactHead(server, '/repo')
-        module.expectArtifactSha1Get(server, '/repo')
-        module.expectArtifactGet(server, '/repo')
-
-        run 'retrieve'
-
-        then:
-        def changedJarFile = file('build/projectA-1.1.jar')
-        changedJarFile.assertHasChangedSince(snapshot)
-        changedJarFile.assertIsCopyOf(module.jarFile)
-    }
-
-    def "caches changing module descriptor and artifacts until cache expiry"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-
-configurations { compile }
-
-
-if (project.hasProperty('doNotCacheChangingModules')) {
-    configurations.all {
-        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-    }
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.1", changing: true
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when: "Version 1.1 is published"
-        def module = ivyRepo().module("group", "projectA", "1.1").publish()
-
-        and: "Server handles requests"
-        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
-
-        and: "We request 1.1 (changing)"
-        run 'retrieve'
-
-        then: "Version 1.1 jar is downloaded"
-        file('build').assertHasDescendants('projectA-1.1.jar')
-        def jarFile = file('build/projectA-1.1.jar')
-        jarFile.assertIsCopyOf(module.jarFile)
-        def snapshot = jarFile.snapshot()
-
-        when: "Module meta-data is changed and artifacts are modified"
-        module.artifact([name: 'other'])
-        module.publishWithChangedContent()
-
-        and: "We request 1.1 (changing), with module meta-data cached. No server requests."
-        run 'retrieve'
-
-        then: "Original module meta-data and artifacts are used"
-        file('build').assertHasDescendants('projectA-1.1.jar')
-        jarFile.assertHasNotChangedSince(snapshot)
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml.sha1', module.sha1File(module.ivyFile))
-        server.expectHeadThenGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar.sha1', module.sha1File(module.jarFile))
-        server.expectHeadThenGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
-        server.expectHeadThenGet('/repo/group/projectA/1.1/other-1.1.jar', module.moduleDir.file('other-1.1.jar'))
-
-        and: "We request 1.1 (changing) again, with zero expiry for dynamic revision cache"
-        executer.withArguments("-PdoNotCacheChangingModules")
-        run 'retrieve'
-
-        then: "We get new artifacts based on the new meta-data"
-        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar')
-        jarFile.assertHasChangedSince(snapshot)
-        jarFile.assertIsCopyOf(module.jarFile)
-    }
-
-    def "can use cache-control DSL to mimic changing pattern for ivy repository"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-
-configurations { compile }
-
-import static java.util.concurrent.TimeUnit.SECONDS
-configurations.all {
-    resolutionStrategy.resolutionRules.with {
-        eachModule({ moduleResolve ->
-            if (moduleResolve.request.version.endsWith('-CHANGING')) {
-                moduleResolve.cacheFor(0, SECONDS)
-            }
-        } as Action)
-
-        eachArtifact({ artifactResolve ->
-            if (artifactResolve.request.moduleVersionIdentifier.version.endsWith('-CHANGING')) {
-                artifactResolve.cacheFor(0, SECONDS)
-            }
-        } as Action)
-    }
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1-CHANGING"
-}
-
-task retrieve(type: Copy) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when: "Version 1-CHANGING is published"
-        def module = ivyRepo().module("group", "projectA", "1-CHANGING").publish()
-
-        and: "Server handles requests"
-        server.expectGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar', module.jarFile)
-
-        and: "We request 1-CHANGING"
-        run 'retrieve'
-
-        then: "Version 1-CHANGING jar is used"
-        file('build').assertHasDescendants('projectA-1-CHANGING.jar')
-        def jarFile = file('build/projectA-1-CHANGING.jar')
-        jarFile.assertIsCopyOf(module.jarFile)
-        def snapshot = jarFile.snapshot()
-
-        when: "Module meta-data is changed and artifacts are modified"
-        module.artifact([name: 'other'])
-        module.publishWithChangedContent()
-
-        and: "Server handles requests"
-        server.resetExpectations()
-        // Server will be hit to get updated versions
-        server.expectGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml.sha1', module.sha1File(module.ivyFile))
-        server.expectHeadThenGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar.sha1', module.sha1File(module.jarFile))
-        server.expectHeadThenGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar', module.jarFile)
-        server.expectHeadThenGet('/repo/group/projectA/1-CHANGING/other-1-CHANGING.jar', module.moduleDir.file('other-1-CHANGING.jar'))
-
-        and: "We request 1-CHANGING again"
-        executer.withArguments()
-        run 'retrieve'
-
-        then: "We get new artifacts based on the new meta-data"
-        file('build').assertHasDescendants('projectA-1-CHANGING.jar', 'other-1-CHANGING.jar')
-        jarFile.assertHasChangedSince(snapshot)
-        jarFile.assertIsCopyOf(module.jarFile)
-    }
-
-    def "avoid redownload unchanged artifact when no checksum available"() {
-        server.start()
-
-        given:
-        buildFile << """
-            repositories {
-                ivy { url "http://localhost:${server.port}/repo" }
-            }
-
-            configurations { compile }
-
-            configurations.all {
-                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-            }
-
-            dependencies {
-                compile group: "group", name: "projectA", version: "1.1", changing: true
-            }
-
-            task retrieve(type: Copy) {
-                into 'build'
-                from configurations.compile
-            }
-        """
-
-        and:
-        def module = ivyRepo().module("group", "projectA", "1.1").publish()
-        
-        // Set the last modified to something that's not going to be anything “else”.
-        // There are lots of dates floating around in a resolution and we want to make
-        // sure we use this.
-        module.jarFile.setLastModified(2000)
-        module.ivyFile.setLastModified(6000)
-
-        def base = "/repo/group/projectA/1.1"
-        def ivyPath = "$base/$module.ivyFile.name"
-        def ivySha1Path = "${ivyPath}.sha1"
-        def originalIvyLastMod = module.ivyFile.lastModified()
-        def originalIvyContentLength = module.ivyFile.length()
-        def jarPath = "$base/$module.jarFile.name"
-        def jarSha1Path = "${jarPath}.sha1"
-        def originalJarLastMod = module.jarFile.lastModified()
-        def originalJarContentLength = module.jarFile.length()
-
-        when:
-        server.expectGet(ivyPath, module.ivyFile)
-        server.expectGet(jarPath, module.jarFile)
-
-        run 'retrieve'
-
-        then:
-        def downloadedJar = file('build/projectA-1.1.jar')
-        downloadedJar.assertIsCopyOf(module.jarFile)
-        def snapshot = downloadedJar.snapshot()
-
-        // Do change the jar, so we can check that the new version wasn't downloaded
-        module.publishWithChangedContent()
-
-        when:
-        server.resetExpectations()
-        server.expectGetMissing(ivySha1Path)
-        server.expectHead(ivyPath, module.ivyFile, originalIvyLastMod, originalIvyContentLength)
-        server.expectGetMissing(jarSha1Path)
-        server.expectHead(jarPath, module.jarFile, originalJarLastMod, originalJarContentLength)
-
-        run 'retrieve'
-
-        then:
-        downloadedJar.assertHasNotChangedSince(snapshot)
-
-        when:
-        server.resetExpectations()
-        server.expectGetMissing(ivySha1Path)
-        server.expectHead(ivyPath, module.ivyFile)
-        server.expectGet(ivyPath, module.ivyFile)
-        server.expectGetMissing(jarSha1Path)
-        server.expectHead(jarPath, module.jarFile)
-        server.expectGet(jarPath, module.jarFile)
-
-        run 'retrieve'
-
-        then:
-        downloadedJar.assertHasChangedSince(snapshot)
-        downloadedJar.assertIsCopyOf(module.jarFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
new file mode 100644
index 0000000..b901fe1
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyChangingModuleRemoteResolveIntegrationTest.groovy
@@ -0,0 +1,423 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyChangingModuleRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "detects changed module descriptor when flagged as changing"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1.1 is published"
+        def module = ivyRepo().module("group", "projectA", "1.1")
+        module.publish()
+
+        and: "Server handles requests"
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+
+        and: "We request 1.1 (changing)"
+        run 'retrieve'
+
+        then: "Version 1.1 jar is downloaded"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+
+        when: "Module meta-data is changed (new artifact)"
+        module.artifact([name: 'other'])
+        module.dependsOn("group", "projectB", "2.0")
+        module.publish()
+        def moduleB = ivyRepo().module("group", "projectB", "2.0")
+        moduleB.publish();
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml.sha1', module.sha1File(module.ivyFile))
+        server.expectHeadThenGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar.sha1', module.sha1File(module.jarFile))
+        server.expectHeadThenGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+        server.expectGet('/repo/group/projectA/1.1/other-1.1.jar', module.moduleDir.file('other-1.1.jar'))
+        server.expectGet('/repo/group/projectB/2.0/ivy-2.0.xml', moduleB.ivyFile)
+        server.expectGet('/repo/group/projectB/2.0/projectB-2.0.jar', moduleB.jarFile)
+
+        and: "We request 1.1 again"
+        run 'retrieve'
+
+        then: "We get all artifacts, including the new ones"
+        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar', 'projectB-2.0.jar')
+    }
+
+    def "can mark a module as changing after first retrieval"() {
+        server.start()
+
+        given:
+        buildFile << """
+def isChanging = project.hasProperty('isChanging') ? true : false
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: isChanging
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+        and:
+        def module = ivyRepo().module("group", "projectA", "1.1")
+        module.publish()
+        server.allowGet('/repo', ivyRepo().rootDir)
+
+        when: 'original retrieve'
+        run 'retrieve'
+
+        then:
+        def jarSnapshot = file('build/projectA-1.1.jar').snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        and:
+        executer.withArguments('-PisChanging')
+        run 'retrieve'
+
+        then:
+        file('build/projectA-1.1.jar').assertHasChangedSince(jarSnapshot)
+    }
+
+    def "detects changed artifact when flagged as changing"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        and:
+        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+
+        when:
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+
+        run 'retrieve'
+
+        then:
+        def jarFile = file('build/projectA-1.1.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when:
+        module.publishWithChangedContent()
+
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        module.expectIvyHead(server, '/repo')
+        module.expectIvySha1Get(server, '/repo')
+        module.expectIvyGet(server, '/repo')
+        module.expectArtifactHead(server, '/repo')
+        module.expectArtifactSha1Get(server, '/repo')
+        module.expectArtifactGet(server, '/repo')
+
+        run 'retrieve'
+
+        then:
+        def changedJarFile = file('build/projectA-1.1.jar')
+        changedJarFile.assertHasChangedSince(snapshot)
+        changedJarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "caches changing module descriptor and artifacts until cache expiry"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+
+if (project.hasProperty('doNotCacheChangingModules')) {
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.1", changing: true
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1.1 is published"
+        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+
+        and: "Server handles requests"
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+
+        and: "We request 1.1 (changing)"
+        run 'retrieve'
+
+        then: "Version 1.1 jar is downloaded"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+        def jarFile = file('build/projectA-1.1.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when: "Module meta-data is changed and artifacts are modified"
+        module.artifact([name: 'other'])
+        module.publishWithChangedContent()
+
+        and: "We request 1.1 (changing), with module meta-data cached. No server requests."
+        run 'retrieve'
+
+        then: "Original module meta-data and artifacts are used"
+        file('build').assertHasDescendants('projectA-1.1.jar')
+        jarFile.assertHasNotChangedSince(snapshot)
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        server.expectGet('/repo/group/projectA/1.1/ivy-1.1.xml.sha1', module.sha1File(module.ivyFile))
+        server.expectHeadThenGet('/repo/group/projectA/1.1/ivy-1.1.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.1/projectA-1.1.jar.sha1', module.sha1File(module.jarFile))
+        server.expectHeadThenGet('/repo/group/projectA/1.1/projectA-1.1.jar', module.jarFile)
+        server.expectGet('/repo/group/projectA/1.1/other-1.1.jar', module.moduleDir.file('other-1.1.jar'))
+
+        and: "We request 1.1 (changing) again, with zero expiry for dynamic revision cache"
+        executer.withArguments("-PdoNotCacheChangingModules")
+        run 'retrieve'
+
+        then: "We get new artifacts based on the new meta-data"
+        file('build').assertHasDescendants('projectA-1.1.jar', 'other-1.1.jar')
+        jarFile.assertHasChangedSince(snapshot)
+        jarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "can use cache-control DSL to mimic changing pattern for ivy repository"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+import static java.util.concurrent.TimeUnit.SECONDS
+configurations.all {
+    resolutionStrategy.resolutionRules.with {
+        eachModule({ moduleResolve ->
+            if (moduleResolve.request.version.endsWith('-CHANGING')) {
+                moduleResolve.cacheFor(0, SECONDS)
+            }
+        } as Action)
+
+        eachArtifact({ artifactResolve ->
+            if (artifactResolve.request.moduleVersionIdentifier.version.endsWith('-CHANGING')) {
+                artifactResolve.cacheFor(0, SECONDS)
+            }
+        } as Action)
+    }
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1-CHANGING"
+}
+
+task retrieve(type: Copy) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Version 1-CHANGING is published"
+        def module = ivyRepo().module("group", "projectA", "1-CHANGING").publish()
+
+        and: "Server handles requests"
+        server.expectGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar', module.jarFile)
+
+        and: "We request 1-CHANGING"
+        run 'retrieve'
+
+        then: "Version 1-CHANGING jar is used"
+        file('build').assertHasDescendants('projectA-1-CHANGING.jar')
+        def jarFile = file('build/projectA-1-CHANGING.jar')
+        jarFile.assertIsCopyOf(module.jarFile)
+        def snapshot = jarFile.snapshot()
+
+        when: "Module meta-data is changed and artifacts are modified"
+        module.artifact([name: 'other'])
+        module.publishWithChangedContent()
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        // Server will be hit to get updated versions
+        server.expectGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml.sha1', module.sha1File(module.ivyFile))
+        server.expectHeadThenGet('/repo/group/projectA/1-CHANGING/ivy-1-CHANGING.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar.sha1', module.sha1File(module.jarFile))
+        server.expectHeadThenGet('/repo/group/projectA/1-CHANGING/projectA-1-CHANGING.jar', module.jarFile)
+        server.expectGet('/repo/group/projectA/1-CHANGING/other-1-CHANGING.jar', module.moduleDir.file('other-1-CHANGING.jar'))
+
+        and: "We request 1-CHANGING again"
+        executer.withArguments()
+        run 'retrieve'
+
+        then: "We get new artifacts based on the new meta-data"
+        file('build').assertHasDescendants('projectA-1-CHANGING.jar', 'other-1-CHANGING.jar')
+        jarFile.assertHasChangedSince(snapshot)
+        jarFile.assertIsCopyOf(module.jarFile)
+    }
+
+    def "avoid redownload unchanged artifact when no checksum available"() {
+        server.start()
+
+        given:
+        buildFile << """
+            repositories {
+                ivy { url "http://localhost:${server.port}/repo" }
+            }
+
+            configurations { compile }
+
+            configurations.all {
+                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+            }
+
+            dependencies {
+                compile group: "group", name: "projectA", version: "1.1", changing: true
+            }
+
+            task retrieve(type: Copy) {
+                into 'build'
+                from configurations.compile
+            }
+        """
+
+        and:
+        def module = ivyRepo().module("group", "projectA", "1.1").publish()
+        
+        // Set the last modified to something that's not going to be anything “else”.
+        // There are lots of dates floating around in a resolution and we want to make
+        // sure we use this.
+        module.jarFile.setLastModified(2000)
+        module.ivyFile.setLastModified(6000)
+
+        def base = "/repo/group/projectA/1.1"
+        def ivyPath = "$base/$module.ivyFile.name"
+        def ivySha1Path = "${ivyPath}.sha1"
+        def originalIvyLastMod = module.ivyFile.lastModified()
+        def originalIvyContentLength = module.ivyFile.length()
+        def jarPath = "$base/$module.jarFile.name"
+        def jarSha1Path = "${jarPath}.sha1"
+        def originalJarLastMod = module.jarFile.lastModified()
+        def originalJarContentLength = module.jarFile.length()
+
+        when:
+        server.expectGet(ivyPath, module.ivyFile)
+        server.expectGet(jarPath, module.jarFile)
+
+        run 'retrieve'
+
+        then:
+        def downloadedJar = file('build/projectA-1.1.jar')
+        downloadedJar.assertIsCopyOf(module.jarFile)
+        def snapshot = downloadedJar.snapshot()
+
+        // Do change the jar, so we can check that the new version wasn't downloaded
+        module.publishWithChangedContent()
+
+        when:
+        server.resetExpectations()
+        server.expectHead(ivyPath, module.ivyFile, originalIvyLastMod, originalIvyContentLength)
+        server.expectHead(jarPath, module.jarFile, originalJarLastMod, originalJarContentLength)
+
+        run 'retrieve'
+
+        then:
+        downloadedJar.assertHasNotChangedSince(snapshot)
+
+        when:
+        server.resetExpectations()
+        server.expectGetMissing(ivySha1Path)
+        server.expectHead(ivyPath, module.ivyFile)
+        server.expectGet(ivyPath, module.ivyFile)
+        server.expectGetMissing(jarSha1Path)
+        server.expectHead(jarPath, module.jarFile)
+        server.expectGet(jarPath, module.jarFile)
+
+        run 'retrieve'
+
+        then:
+        downloadedJar.assertHasChangedSince(snapshot)
+        downloadedJar.assertIsCopyOf(module.jarFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDependencyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDependencyResolveIntegrationTest.groovy
deleted file mode 100644
index c57f0dd..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDependencyResolveIntegrationTest.groovy
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyDependencyResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "dependency includes all artifacts and transitive dependencies of referenced configuration"() {
-        given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: "classifier")
-        module.artifact(name: "test-extra")
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { ivy { url "${repo.uri}" } }
-configurations { compile }
-dependencies {
-    compile "org.gradle:test:1.45"
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'test-1.45-classifier.jar', 'test-extra-1.45.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "dependency that references a classifier includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
-        given:
-        def repo = ivyRepo()
-        repo.module("org.gradle", "test", "1.45")
-                .dependsOn("org.gradle", "other", "preview-1")
-                .artifact(classifier: "classifier")
-                .artifact(name: "test-extra")
-                .publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { ivy { url "${repo.uri}" } }
-configurations { compile }
-dependencies {
-    compile "org.gradle:test:1.45:classifier"
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
-        given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: "classifier")
-        module.artifact(name: "test-extra")
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
-
-        and:
-        buildFile << """
-repositories { ivy { url "${repo.uri}" } }
-configurations { compile }
-dependencies {
-    compile ("org.gradle:test:1.45") {
-        artifact {
-            name = 'test-extra'
-            type = 'jar'
-        }
-    }
-}
-
-task check << {
-    assert configurations.compile.collect { it.name } == ['test-extra-1.45.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-
-    def "transitive flag of referenced configuration affects its transitive dependencies only"() {
-        given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.nonTransitive('default')
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").dependsOn("org.gradle", "other2", "7").publish()
-        repo.module("org.gradle", "other2", "7").publish()
-
-        and:
-        buildFile << """
-repositories { ivy { url "${repo.uri}" } }
-configurations {
-    compile
-    runtime.extendsFrom compile
-}
-dependencies {
-    compile "org.gradle:test:1.45"
-    runtime "org.gradle:other:preview-1"
-}
-
-task check << {
-    def spec = { it.name == 'test' } as Spec
-
-    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
-    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
-
-    assert configurations.runtime.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar', 'other2-7.jar']
-    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
-}
-"""
-
-        expect:
-        succeeds "check"
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolutionIntegrationTest.groovy
deleted file mode 100644
index 7abfb00..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.fixtures.IvyModule
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyDynamicRevisionRemoteResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "uses latest version from version range and latest status"() {
-        server.start()
-        def repo = ivyRepo()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-    compile group: "group", name: "projectB", version: "latest.integration"
-}
-
-configurations.all {
-    resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when: "Version 1.1 is published"
-        def projectA1 = repo.module("group", "projectA", "1.1").publish()
-        repo.module("group", "projectA", "2.0").publish()
-        def projectB1 = repo.module("group", "projectB", "1.1").publish()
-
-        and: "Server handles requests"
-        serveUpDynamicRevision(projectA1)
-        serveUpDynamicRevision(projectB1)
-
-        and:
-        run 'retrieve'
-
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
-
-        when: "New versions are published"
-        def projectA2 = repo.module("group", "projectA", "1.2").publish()
-        def projectB2 = repo.module("group", "projectB", "2.2").publish()
-
-        and: "Server handles requests"
-        server.resetExpectations()
-        serveUpDynamicRevision(projectA2)
-        serveUpDynamicRevision(projectB2)
-
-        and:
-        run 'retrieve'
-
-        then: "New versions are used"
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
-        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
-    }
-
-
-    def "determines latest version with jar only"() {
-        server.start()
-        def repo = ivyRepo()
-
-        given:
-        buildFile << """
-repositories {
-  ivy {
-      url "http://localhost:${server.port}"
-  }
-}
-
-configurations { compile }
-
-dependencies {
-  compile group: "group", name: "projectA", version: "1.+"
-}
-
-task retrieve(type: Sync) {
-  from configurations.compile
-  into 'libs'
-}
-"""
-
-        when: "Version 1.1 is published"
-        def projectA11 = repo.module("group", "projectA", "1.1").publish()
-        def projectA12 = repo.module("group", "projectA", "1.2").publish()
-        repo.module("group", "projectA", "2.0").publish()
-
-        and: "Server handles requests"
-        server.expectGetDirectoryListing("/${projectA12.organisation}/${projectA12.module}/", projectA12.moduleDir.parentFile)
-        server.expectGetMissing("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/ivy-${projectA12.revision}.xml")
-        server.expectGetMissing("/${projectA11.organisation}/${projectA11.module}/${projectA11.revision}/ivy-${projectA11.revision}.xml")
-
-        // TODO:DAZ Should not list twice
-        server.expectGetDirectoryListing("/${projectA12.organisation}/${projectA12.module}/", projectA12.moduleDir.parentFile)
-        server.expectHead("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/${projectA12.module}-${projectA12.revision}.jar", projectA12.jarFile)
-        server.expectGet("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/${projectA12.module}-${projectA12.revision}.jar", projectA12.jarFile)
-
-        and:
-        run 'retrieve'
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    def "uses latest version with correct status for latest.release and latest.milestone"() {
-        server.start()
-        def repo = ivyRepo()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}/repo"
-    }
-}
-
-configurations {
-    release
-    milestone
-}
-
-configurations.all {
-    resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
-}
-
-dependencies {
-    release group: "group", name: "projectA", version: "latest.release"
-    milestone group: "group", name: "projectA", version: "latest.milestone"
-}
-
-task retrieve(dependsOn: ['retrieveRelease', 'retrieveMilestone'])
-
-task retrieveRelease(type: Sync) {
-    from configurations.release
-    into 'release'
-}
-
-task retrieveMilestone(type: Sync) {
-    from configurations.milestone
-    into 'milestone'
-}
-"""
-
-        when: "Versions are published"
-        repo.module("group", "projectA", "1.0").withStatus('release').publish()
-        repo.module('group', 'projectA', '1.1').withStatus('milestone').publish()
-        repo.module('group', 'projectA', '1.2').withStatus('integration').publish()
-        repo.module("group", "projectA", "2.0").withStatus('release').publish()
-        repo.module('group', 'projectA', '2.1').withStatus('milestone').publish()
-        repo.module('group', 'projectA', '2.2').withStatus('integration').publish()
-
-        and: "Server handles requests"
-        server.allowGet('/repo', repo.rootDir)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('release').assertHasDescendants('projectA-2.0.jar')
-        file('milestone').assertHasDescendants('projectA-2.1.jar')
-    }
-
-    def "checks new repositories before returning any cached value"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo1" }
-}
-
-if (project.hasProperty('addRepo2')) {
-    repositories {
-        ivy { url "http://localhost:${server.port}/repo2" }
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when:
-        def projectA11 = ivyRepo('repo1').module("group", "projectA", "1.1")
-        projectA11.publish()
-        def projectA12 = ivyRepo('repo2').module("group", "projectA", "1.2")
-        projectA12.publish()
-
-        and: "Server handles requests"
-        serveUpDynamicRevision(projectA11, "/repo1")
-
-        and: "Retrieve with only repo1"
-        run 'retrieve'
-
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        serveUpDynamicRevision(projectA12, "/repo2")
-
-        and: "Retrieve with both repos"
-        executer.withArguments("-PaddRepo2")
-        run 'retrieve'
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-
-    def "uses and caches latest of versions obtained from multiple HTTP repositories"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo1" }
-    ivy { url "http://localhost:${server.port}/repo2" }
-    ivy { url "http://localhost:${server.port}/repo3" }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when: "Versions are published"
-        def projectA11 = ivyRepo('repo1').module("group", "projectA", "1.1")
-        projectA11.publish()
-        def projectA12 = ivyRepo('repo3').module("group", "projectA", "1.2")
-        projectA12.publish()
-
-        and: "Server handles requests"
-        serveUpDynamicRevision(projectA11, "/repo1")
-        // TODO Should only list missing directory once
-        server.expectGetMissing("/repo2/group/projectA/")
-        server.expectGetMissing("/repo2/group/projectA/")
-        serveUpDynamicRevision(projectA12, "/repo3")
-
-        and:
-        run 'retrieve'
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-
-        when: "Run again with cached dependencies"
-        server.resetExpectations()
-        def result = run 'retrieve'
-
-        then: "No server requests, task skipped"
-        result.assertTaskSkipped(':retrieve')
-    }
-
-    def "caches resolved revisions until cache expiry"() {
-        server.start()
-        def repo = ivyRepo()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-}
-
-if (project.hasProperty('noDynamicRevisionCache')) {
-    configurations.all {
-        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
-    }
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when: "Version 1.1 is published"
-        def version1 = repo.module("group", "projectA", "1.1")
-        version1.publish()
-
-        and: "Server handles requests"
-        serveUpDynamicRevision(version1)
-
-        and: "We request 1.+"
-        run 'retrieve'
-
-        then: "Version 1.1 is used"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
-
-        when: "Version 1.2 is published"
-        def version2 = repo.module("group", "projectA", "1.2")
-        version2.publish()
-
-        and: "We request 1.+, with dynamic mappings cached. No server requests."
-        run 'retrieve'
-
-        then: "Version 1.1 is still used, as the 1.+ -> 1.1 mapping is cached"
-        file('libs').assertHasDescendants('projectA-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
-
-        when: "Server handles requests"
-        serveUpDynamicRevision(version2)
-
-        and: "We request 1.+, with zero expiry for dynamic revision cache"
-        executer.withDeprecationChecksDisabled().withArguments("-d", "-PnoDynamicRevisionCache").withTasks('retrieve').run()
-
-        then: "Version 1.2 is used"
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(version2.jarFile)
-    }
-
-    def "uses and caches dynamic revisions for transitive dependencies"() {
-        server.start()
-        def repo = ivyRepo()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile group: "group", name: "main", version: "1.0"
-}
-
-if (project.hasProperty('noDynamicRevisionCache')) {
-    configurations.all {
-        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
-    }
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when: "Version is published"
-        def mainProject = repo.module("group", "main", "1.0")
-        mainProject.dependsOn("group", "projectA", "1.+")
-        mainProject.dependsOn("group", "projectB", "latest.integration")
-        mainProject.publish()
-
-        and: "transitive dependencies have initial values"
-        def projectA1 = repo.module("group", "projectA", "1.1")
-        projectA1.publish()
-        def projectB1 = repo.module("group", "projectB", "1.1")
-        projectB1.publish()
-
-        and: "Server handles requests"
-        server.expectGet("/group/main/1.0/ivy-1.0.xml", mainProject.ivyFile)
-        server.expectGet("/group/main/1.0/main-1.0.jar", mainProject.jarFile)
-        serveUpDynamicRevision(projectA1)
-        serveUpDynamicRevision(projectB1)
-
-        and:
-        run 'retrieve'
-
-        then: "Initial transitive dependencies are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
-
-        when: "New versions are published"
-        def projectA2 = repo.module("group", "projectA", "1.2")
-        projectA2.publish()
-        def projectB2 = repo.module("group", "projectB", "2.2")
-        projectB2.publish()
-
-        and: "No server requests"
-        server.resetExpectations()
-
-        and:
-        run 'retrieve'
-
-        then: "Cached versions are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        serveUpDynamicRevision(projectA2)
-        serveUpDynamicRevision(projectB2)
-
-        and: "DynamicRevisionCache is bypassed"
-        executer.withArguments("-PnoDynamicRevisionCache")
-        run 'retrieve'
-
-        then: "New versions are used"
-        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.2.jar', 'projectB-2.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
-        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
-    }
-
-    private def serveUpDynamicRevision(IvyModule module, String prefix = "") {
-        server.expectGetDirectoryListing("${prefix}/${module.organisation}/${module.module}/", module.moduleDir.parentFile)
-        server.expectGet("${prefix}/${module.organisation}/${module.module}/${module.revision}/ivy-${module.revision}.xml", module.ivyFile)
-        server.expectGet("${prefix}/${module.organisation}/${module.module}/${module.revision}/${module.module}-${module.revision}.jar", module.jarFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
new file mode 100644
index 0000000..7f63c13
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDynamicRevisionRemoteResolveIntegrationTest.groovy
@@ -0,0 +1,457 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.fixtures.IvyModule
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyDynamicRevisionRemoteResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "uses latest version from version range and latest status"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+    compile group: "group", name: "projectB", version: "latest.integration"
+}
+
+configurations.all {
+    resolutionStrategy.cacheDynamicVersionsFor 0, "seconds"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def projectA1 = repo.module("group", "projectA", "1.1").publish()
+        repo.module("group", "projectA", "2.0").publish()
+        def projectB1 = repo.module("group", "projectB", "1.1").publish()
+
+        and: "Server handles requests"
+        serveUpDynamicRevision(projectA1)
+        serveUpDynamicRevision(projectB1)
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "New versions are published"
+        def projectA2 = repo.module("group", "projectA", "1.2").publish()
+        def projectB2 = repo.module("group", "projectB", "2.2").publish()
+
+        and: "Server handles requests"
+        server.resetExpectations()
+        serveUpDynamicRevision(projectA2)
+        serveUpDynamicRevision(projectB2)
+
+        and:
+        run 'retrieve'
+
+        then: "New versions are used"
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
+    }
+
+
+    def "determines latest version with jar only"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+  ivy {
+      url "http://localhost:${server.port}"
+  }
+}
+
+configurations { compile }
+
+dependencies {
+  compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def projectA11 = repo.module("group", "projectA", "1.1").publish()
+        def projectA12 = repo.module("group", "projectA", "1.2").publish()
+        repo.module("group", "projectA", "2.0").publish()
+
+        and: "Server handles requests"
+        server.expectGetDirectoryListing("/${projectA12.organisation}/${projectA12.module}/", projectA12.moduleDir.parentFile)
+        server.expectGetMissing("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/ivy-${projectA12.revision}.xml")
+        server.expectGetMissing("/${projectA11.organisation}/${projectA11.module}/${projectA11.revision}/ivy-${projectA11.revision}.xml")
+
+        // TODO:DAZ Should not list twice
+        server.expectGetDirectoryListing("/${projectA12.organisation}/${projectA12.module}/", projectA12.moduleDir.parentFile)
+        server.expectHead("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/${projectA12.module}-${projectA12.revision}.jar", projectA12.jarFile)
+        server.expectGet("/${projectA12.organisation}/${projectA12.module}/${projectA12.revision}/${projectA12.module}-${projectA12.revision}.jar", projectA12.jarFile)
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    def "uses latest version with correct status for latest.release and latest.milestone"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}/repo"
+    }
+}
+
+configurations {
+    release
+    milestone
+}
+
+configurations.all {
+    resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+}
+
+dependencies {
+    release group: "group", name: "projectA", version: "latest.release"
+    milestone group: "group", name: "projectA", version: "latest.milestone"
+}
+
+task retrieve(dependsOn: ['retrieveRelease', 'retrieveMilestone'])
+
+task retrieveRelease(type: Sync) {
+    from configurations.release
+    into 'release'
+}
+
+task retrieveMilestone(type: Sync) {
+    from configurations.milestone
+    into 'milestone'
+}
+"""
+
+        when: "Versions are published"
+        repo.module("group", "projectA", "1.0").withStatus('release').publish()
+        repo.module('group', 'projectA', '1.1').withStatus('milestone').publish()
+        repo.module('group', 'projectA', '1.2').withStatus('integration').publish()
+        repo.module("group", "projectA", "2.0").withStatus('release').publish()
+        repo.module('group', 'projectA', '2.1').withStatus('milestone').publish()
+        repo.module('group', 'projectA', '2.2').withStatus('integration').publish()
+
+        and: "Server handles requests"
+        server.allowGet('/repo', repo.rootDir)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('release').assertHasDescendants('projectA-2.0.jar')
+        file('milestone').assertHasDescendants('projectA-2.1.jar')
+    }
+
+    def "checks new repositories before returning any cached value"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo1" }
+}
+
+if (project.hasProperty('addRepo2')) {
+    repositories {
+        ivy { url "http://localhost:${server.port}/repo2" }
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        def projectA11 = ivyRepo('repo1').module("group", "projectA", "1.1")
+        projectA11.publish()
+        def projectA12 = ivyRepo('repo2').module("group", "projectA", "1.2")
+        projectA12.publish()
+
+        and: "Server handles requests"
+        serveUpDynamicRevision(projectA11, "/repo1")
+
+        and: "Retrieve with only repo1"
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        serveUpDynamicRevision(projectA12, "/repo2")
+
+        and: "Retrieve with both repos"
+        executer.withArguments("-PaddRepo2")
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+
+    def "uses and caches latest of versions obtained from multiple HTTP repositories"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo1" }
+    ivy { url "http://localhost:${server.port}/repo2" }
+    ivy { url "http://localhost:${server.port}/repo3" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Versions are published"
+        def projectA11 = ivyRepo('repo1').module("group", "projectA", "1.1")
+        projectA11.publish()
+        def projectA12 = ivyRepo('repo3').module("group", "projectA", "1.2")
+        projectA12.publish()
+
+        and: "Server handles requests"
+        server.expectGetDirectoryListing("/repo1/group/projectA/", projectA11.moduleDir.parentFile)
+        // TODO Should not need to get this
+        server.expectGet("/repo1/group/projectA/1.1/ivy-1.1.xml", projectA11.ivyFile)
+        // TODO Should only list missing directory once
+        server.expectGetMissing("/repo2/group/projectA/")
+        server.expectGetMissing("/repo2/group/projectA/")
+        serveUpDynamicRevision(projectA12, "/repo3")
+
+        and:
+        run 'retrieve'
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+
+        when: "Run again with cached dependencies"
+        server.resetExpectations()
+        def result = run 'retrieve'
+
+        then: "No server requests, task skipped"
+        result.assertTaskSkipped(':retrieve')
+    }
+
+    def "caches resolved revisions until cache expiry"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+}
+
+if (project.hasProperty('noDynamicRevisionCache')) {
+    configurations.all {
+        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+    }
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version 1.1 is published"
+        def version1 = repo.module("group", "projectA", "1.1")
+        version1.publish()
+
+        and: "Server handles requests"
+        serveUpDynamicRevision(version1)
+
+        and: "We request 1.+"
+        run 'retrieve'
+
+        then: "Version 1.1 is used"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
+
+        when: "Version 1.2 is published"
+        def version2 = repo.module("group", "projectA", "1.2")
+        version2.publish()
+
+        and: "We request 1.+, with dynamic mappings cached. No server requests."
+        run 'retrieve'
+
+        then: "Version 1.1 is still used, as the 1.+ -> 1.1 mapping is cached"
+        file('libs').assertHasDescendants('projectA-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(version1.jarFile)
+
+        when: "Server handles requests"
+        serveUpDynamicRevision(version2)
+
+        and: "We request 1.+, with zero expiry for dynamic revision cache"
+        executer.withArguments("-d", "-PnoDynamicRevisionCache").withTasks('retrieve').run()
+
+        then: "Version 1.2 is used"
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(version2.jarFile)
+    }
+
+    def "uses and caches dynamic revisions for transitive dependencies"() {
+        server.start()
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile group: "group", name: "main", version: "1.0"
+}
+
+if (project.hasProperty('noDynamicRevisionCache')) {
+    configurations.all {
+        resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
+    }
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when: "Version is published"
+        def mainProject = repo.module("group", "main", "1.0")
+        mainProject.dependsOn("group", "projectA", "1.+")
+        mainProject.dependsOn("group", "projectB", "latest.integration")
+        mainProject.publish()
+
+        and: "transitive dependencies have initial values"
+        def projectA1 = repo.module("group", "projectA", "1.1")
+        projectA1.publish()
+        def projectB1 = repo.module("group", "projectB", "1.1")
+        projectB1.publish()
+
+        and: "Server handles requests"
+        server.expectGet("/group/main/1.0/ivy-1.0.xml", mainProject.ivyFile)
+        server.expectGet("/group/main/1.0/main-1.0.jar", mainProject.jarFile)
+        serveUpDynamicRevision(projectA1)
+        serveUpDynamicRevision(projectB1)
+
+        and:
+        run 'retrieve'
+
+        then: "Initial transitive dependencies are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "New versions are published"
+        def projectA2 = repo.module("group", "projectA", "1.2")
+        projectA2.publish()
+        def projectB2 = repo.module("group", "projectB", "2.2")
+        projectB2.publish()
+
+        and: "No server requests"
+        server.resetExpectations()
+
+        and:
+        run 'retrieve'
+
+        then: "Cached versions are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.1.jar', 'projectB-1.1.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.1.jar').assertIsCopyOf(projectB1.jarFile)
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        serveUpDynamicRevision(projectA2)
+        serveUpDynamicRevision(projectB2)
+
+        and: "DynamicRevisionCache is bypassed"
+        executer.withArguments("-PnoDynamicRevisionCache")
+        run 'retrieve'
+
+        then: "New versions are used"
+        file('libs').assertHasDescendants('main-1.0.jar', 'projectA-1.2.jar', 'projectB-2.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.2.jar').assertIsCopyOf(projectB2.jarFile)
+    }
+
+    private def serveUpDynamicRevision(IvyModule module, String prefix = "") {
+        server.expectGetDirectoryListing("${prefix}/${module.organisation}/${module.module}/", module.moduleDir.parentFile)
+        server.expectGet("${prefix}/${module.organisation}/${module.module}/${module.revision}/ivy-${module.revision}.xml", module.ivyFile)
+        server.expectGet("${prefix}/${module.organisation}/${module.module}/${module.revision}/${module.module}-${module.revision}.jar", module.jarFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..13983c9
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyFileRepoResolveIntegrationTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyFileRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "does not cache local artifacts or metadata"() {
+        given:
+        def repo = ivyRepo()
+        def moduleA = repo.module('group', 'projectA', '1.2')
+        moduleA.publish()
+        def moduleB = repo.module('group', 'projectB', '9-beta')
+        moduleB.publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy {
+        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9-beta')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-9-beta.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
+        file('libs/projectB-9-beta.jar').assertIsCopyOf(moduleB.jarFile)
+    }
+
+    public void "does not cache resolution of dynamic versions or changing modules"() {
+        def repo = ivyRepo()
+
+        given:
+        buildFile << """
+repositories {
+    ivy {
+        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+    }
+}
+
+configurations {
+    compile
+}
+
+dependencies {
+    compile group: "group", name: "projectA", version: "1.+"
+    compile group: "group", name: "projectB", version: "latest.integration"
+    compile group: "group", name: "projectC", version: "1.0", changing: true
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        def projectA1 = repo.module("group", "projectA", "1.1")
+        projectA1.publish()
+        def projectB1 = repo.module("group", "projectB", "1.0")
+        projectB1.publish()
+        def projectC1 = repo.module("group", "projectC", "1.0")
+        projectC1.publish()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.0.jar', 'projectC-1.0.jar')
+        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
+        file('libs/projectB-1.0.jar').assertIsCopyOf(projectB1.jarFile)
+        def jarC = file('libs/projectC-1.0.jar')
+        jarC.assertIsCopyOf(projectC1.jarFile)
+        def jarCsnapshot = jarC.snapshot()
+
+        when:
+        def projectA2 = repo.module("group", "projectA", "1.2")
+        projectA2.publish()
+        def projectB2 = repo.module("group", "projectB", "2.0")
+        projectB2.publish()
+        projectC1.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.0.jar', 'projectC-1.0.jar')
+        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
+        file('libs/projectB-2.0.jar').assertIsCopyOf(projectB2.jarFile)
+
+        def jarC1 = file('libs/projectC-1.0.jar')
+        jarC1.assertIsCopyOf(projectC1.jarFile)
+        jarC1.assertHasChangedSince(jarCsnapshot)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..5b50f67
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
+        server.start()
+        given:
+        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+        when:
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds 'listJars'
+    }
+
+    public void "can resolve and cache artifact-only dependencies from an HTTP Ivy repository"() {
+        server.start()
+        given:
+        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2 at jar' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+
+        when:
+        // TODO: Should meta-data be fetched for an artifact-only dependency?
+        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "can resolve and cache dependencies from multiple HTTP Ivy repositories"() {
+        server.start()
+        given:
+        def repo = ivyRepo()
+        def moduleA = repo.module('group', 'projectA').publish()
+        def moduleB = repo.module('group', 'projectB').publish()
+        def moduleC = repo.module('group', 'projectC').publish()
+
+        and:
+        buildFile << """
+repositories {
+    ivy { url "http://localhost:${server.port}/repo1" }
+    ivy { url "http://localhost:${server.port}/repo2" }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0', 'group:projectC:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar', 'projectC-1.0.jar']
+}
+"""
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/ivy-1.0.xml', moduleA.ivyFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', moduleA.jarFile)
+
+        // Handles missing in repo1
+        server.expectGetMissing('/repo1/group/projectB/1.0/ivy-1.0.xml')
+        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
+
+        server.expectGet('/repo2/group/projectB/1.0/ivy-1.0.xml', moduleB.ivyFile)
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', moduleB.jarFile)
+
+        // Handles from broken url in repo1 (but does not cache)
+        server.addBroken('/repo1/group/projectC')
+        server.expectGet('/repo2/group/projectC/1.0/ivy-1.0.xml', moduleC.ivyFile)
+        server.expectGet('/repo2/group/projectC/1.0/projectC-1.0.jar', moduleC.jarFile)
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        server.addBroken('/repo1/group/projectC') // Will always re-attempt a broken repository
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    public void "uses all configured patterns to resolve artifacts and caches result"() {
+        server.start()
+
+        given:
+        def module = ivyRepo().module('group', 'projectA', '1.2').publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}/first"
+        artifactPattern "http://localhost:${server.port}/second/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+        artifactPattern "http://localhost:${server.port}/third/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+        ivyPattern "http://localhost:${server.port}/second/[module]/[revision]/ivy.xml"
+        ivyPattern "http://localhost:${server.port}/third/[module]/[revision]/ivy.xml"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+}
+task show << { println configurations.compile.files }
+"""
+
+        when:
+        server.expectGetMissing('/first/group/projectA/1.2/ivy-1.2.xml')
+        server.expectGetMissing('/first/group/projectA/1.2/projectA-1.2.jar')
+        server.expectGetMissing('/second/projectA/1.2/ivy.xml')
+        server.expectGetMissing('/second/projectA/1.2/projectA-1.2.jar')
+        server.expectGet('/third/projectA/1.2/ivy.xml', module.ivyFile)
+        server.expectGet('/third/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        then:
+        succeeds('show')
+
+        when:
+        server.resetExpectations()
+
+        then:
+        succeeds('show')
+    }
+
+    public void "replaces org.name with org/name when using maven layout"() {
+        server.start()
+
+        given:
+        def module = ivyRepo().module('org.name.here', 'projectA', '1.2').publish()
+
+        buildFile << """
+repositories {
+    ivy {
+        url "http://localhost:${server.port}"
+        layout "maven"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'org.name.here:projectA:1.2'
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'libs'
+}
+"""
+
+        when:
+        server.expectGet('/org/name/here/projectA/1.2/ivy-1.2.xml', module.ivyFile)
+        server.expectGet('/org/name/here/projectA/1.2/projectA-1.2.jar', module.jarFile)
+
+        and:
+        succeeds('retrieve')
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar')
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyLocalDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyLocalDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index bb59d8c..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyLocalDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyLocalDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    public void "does not cache local artifacts or metadata"() {
-        given:
-        def repo = ivyRepo()
-        def moduleA = repo.module('group', 'projectA', '1.2')
-        moduleA.publish()
-        def moduleB = repo.module('group', 'projectB', '9-beta')
-        moduleB.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy {
-        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
-
-        when:
-        moduleA.dependsOn('group', 'projectB', '9-beta')
-        moduleA.publishWithChangedContent()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-9-beta.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(moduleA.jarFile)
-        file('libs/projectB-9-beta.jar').assertIsCopyOf(moduleB.jarFile)
-    }
-
-    public void "does not cache resolution of dynamic versions or changing modules"() {
-        def repo = ivyRepo()
-
-        given:
-        buildFile << """
-repositories {
-    ivy {
-        artifactPattern "${repo.uri}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-    }
-}
-
-configurations {
-    compile
-}
-
-dependencies {
-    compile group: "group", name: "projectA", version: "1.+"
-    compile group: "group", name: "projectB", version: "latest.integration"
-    compile group: "group", name: "projectC", version: "1.0", changing: true
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when:
-        def projectA1 = repo.module("group", "projectA", "1.1")
-        projectA1.publish()
-        def projectB1 = repo.module("group", "projectB", "1.0")
-        projectB1.publish()
-        def projectC1 = repo.module("group", "projectC", "1.0")
-        projectC1.publish()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.1.jar', 'projectB-1.0.jar', 'projectC-1.0.jar')
-        file('libs/projectA-1.1.jar').assertIsCopyOf(projectA1.jarFile)
-        file('libs/projectB-1.0.jar').assertIsCopyOf(projectB1.jarFile)
-        def jarC = file('libs/projectC-1.0.jar')
-        jarC.assertIsCopyOf(projectC1.jarFile)
-        def jarCsnapshot = jarC.snapshot()
-
-        when:
-        def projectA2 = repo.module("group", "projectA", "1.2")
-        projectA2.publish()
-        def projectB2 = repo.module("group", "projectB", "2.0")
-        projectB2.publish()
-        projectC1.publishWithChangedContent()
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-2.0.jar', 'projectC-1.0.jar')
-        file('libs/projectA-1.2.jar').assertIsCopyOf(projectA2.jarFile)
-        file('libs/projectB-2.0.jar').assertIsCopyOf(projectB2.jarFile)
-
-        def jarC1 = file('libs/projectC-1.0.jar')
-        jarC1.assertIsCopyOf(projectC1.jarFile)
-        jarC1.assertHasChangedSince(jarCsnapshot)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyRemoteDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyRemoteDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index 2310987..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyRemoteDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.ivy
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class IvyRemoteDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
-        server.start()
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-        when:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds 'listJars'
-
-        when:
-        server.resetExpectations()
-        // No extra calls for cached dependencies
-
-        then:
-        succeeds 'listJars'
-    }
-
-    public void "can resolve and cache artifact-only dependencies from an HTTP Ivy repository"() {
-        server.start()
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2 at jar' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-
-        when:
-        // TODO: Should meta-data be fetched for an artifact-only dependency?
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('listJars')
-
-        when:
-        server.resetExpectations()
-        // No extra calls for cached dependencies
-
-        then:
-        succeeds('listJars')
-    }
-
-    public void "can resolve and cache dependencies from multiple HTTP Ivy repositories"() {
-        server.start()
-        given:
-        def repo = ivyRepo()
-        def moduleA = repo.module('group', 'projectA').publish()
-        def moduleB = repo.module('group', 'projectB').publish()
-        def moduleC = repo.module('group', 'projectC').publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy { url "http://localhost:${server.port}/repo1" }
-    ivy { url "http://localhost:${server.port}/repo2" }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0', 'group:projectB:1.0', 'group:projectC:1.0'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar', 'projectC-1.0.jar']
-}
-"""
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/ivy-1.0.xml', moduleA.ivyFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', moduleA.jarFile)
-
-        // Handles missing in repo1
-        server.expectGetMissing('/repo1/group/projectB/1.0/ivy-1.0.xml')
-        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
-
-        server.expectGet('/repo2/group/projectB/1.0/ivy-1.0.xml', moduleB.ivyFile)
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', moduleB.jarFile)
-
-        // Handles from broken url in repo1 (but does not cache)
-        server.addBroken('/repo1/group/projectC')
-        server.expectGet('/repo2/group/projectC/1.0/ivy-1.0.xml', moduleC.ivyFile)
-        server.expectGet('/repo2/group/projectC/1.0/projectC-1.0.jar', moduleC.jarFile)
-
-        then:
-        succeeds('listJars')
-
-        when:
-        server.resetExpectations()
-        server.addBroken('/repo1/group/projectC') // Will always re-attempt a broken repository
-        // No extra calls for cached dependencies
-
-        then:
-        succeeds('listJars')
-    }
-
-    public void "can resolve dependencies from password protected HTTP Ivy repository"() {
-        server.start()
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}/repo"
-
-        credentials {
-            password 'password'
-            username 'username'
-        }
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        server.expectGet('/repo/group/projectA/1.2/ivy-1.2.xml', 'username', 'password', module.ivyFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', module.jarFile)
-
-        then:
-        succeeds('listJars')
-    }
-
-    public void "uses all configured patterns to resolve artifacts and caches result"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('group', 'projectA', '1.2')
-        module.publish()
-
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}/first"
-        artifactPattern "http://localhost:${server.port}/second/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-        artifactPattern "http://localhost:${server.port}/third/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
-        ivyPattern "http://localhost:${server.port}/second/[module]/[revision]/ivy.xml"
-        ivyPattern "http://localhost:${server.port}/third/[module]/[revision]/ivy.xml"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task show << { println configurations.compile.files }
-"""
-
-        when:
-        server.expectGetMissing('/first/group/projectA/1.2/ivy-1.2.xml')
-        server.expectGetMissing('/first/group/projectA/1.2/projectA-1.2.jar')
-        server.expectGetMissing('/second/projectA/1.2/ivy.xml')
-        server.expectGetMissing('/second/projectA/1.2/projectA-1.2.jar')
-        server.expectGet('/third/projectA/1.2/ivy.xml', module.ivyFile)
-        server.expectGet('/third/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        then:
-        succeeds('show')
-
-        when:
-        server.resetExpectations()
-
-        then:
-        succeeds('show')
-    }
-
-    public void "replaces org.name with org/name when using maven layout"() {
-        server.start()
-
-        given:
-        def repo = ivyRepo()
-        def module = repo.module('org.name.here', 'projectA', '1.2')
-        module.publish()
-
-        buildFile << """
-repositories {
-    ivy {
-        url "http://localhost:${server.port}"
-        layout "maven"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'org.name.here:projectA:1.2'
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'libs'
-}
-"""
-
-        when:
-        server.expectGet('/org/name/here/projectA/1.2/ivy-1.2.xml', module.ivyFile)
-        server.expectGet('/org/name/here/projectA/1.2/projectA-1.2.jar', module.jarFile)
-
-        and:
-        succeeds('retrieve')
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.2.jar')
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
new file mode 100644
index 0000000..5c60cce
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.ivy
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class IvyResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "dependency includes all artifacts and transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: "classifier")
+        module.artifact(name: "test-extra")
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'test-1.45-classifier.jar', 'test-extra-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references a classifier includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        repo.module("org.gradle", "test", "1.45")
+                .dependsOn("org.gradle", "other", "preview-1")
+                .artifact(classifier: "classifier")
+                .artifact(name: "test-extra")
+                .publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45:classifier"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45-classifier.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.artifact(classifier: "classifier")
+        module.artifact(name: "test-extra")
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations { compile }
+dependencies {
+    compile ("org.gradle:test:1.45") {
+        artifact {
+            name = 'test-extra'
+            type = 'jar'
+        }
+    }
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-extra-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+
+    def "transitive flag of referenced configuration affects its transitive dependencies only"() {
+        given:
+        def repo = ivyRepo()
+        def module = repo.module("org.gradle", "test", "1.45")
+        module.dependsOn("org.gradle", "other", "preview-1")
+        module.nonTransitive('default')
+        module.publish()
+        repo.module("org.gradle", "other", "preview-1").dependsOn("org.gradle", "other2", "7").publish()
+        repo.module("org.gradle", "other2", "7").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${repo.uri}" } }
+configurations {
+    compile
+    runtime.extendsFrom compile
+}
+dependencies {
+    compile "org.gradle:test:1.45"
+    runtime "org.gradle:other:preview-1"
+}
+
+task check << {
+    def spec = { it.name == 'test' } as Spec
+
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+
+    assert configurations.runtime.collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar', 'other2-7.jar']
+    assert configurations.compile.resolvedConfiguration.getFiles(spec).collect { it.name } == ['test-1.45.jar', 'other-preview-1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileDependenciesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileDependenciesIntegrationTest.groovy
deleted file mode 100644
index 52d2d76..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileDependenciesIntegrationTest.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import spock.lang.Issue
-
-class BadPomFileDependenciesIntegrationTest extends AbstractDependencyResolutionTest {
-
-    @Issue("http://issues.gradle.org/browse/GRADLE-1005")
-    def "can handle self referencing dependency"() {
-        given:
-        file("settings.gradle") << "include 'client'"
-
-        and:
-        mavenRepo().module('group', 'artifact', '1.0').dependsOn('group', 'artifact', '1.0').publish()
-
-        and:
-        buildFile << """
-            repositories {
-                maven { url "${mavenRepo().uri}" }
-            }
-            configurations { compile }
-            dependencies {
-                compile "group:artifact:1.0"
-            }
-            task libs << { assert configurations.compile.files.collect {it.name} == ['artifact-1.0.jar'] }
-        """
-
-        expect:
-        succeeds ":libs"
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
new file mode 100644
index 0000000..a94a992
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/BadPomFileResolveIntegrationTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import spock.lang.Issue
+
+class BadPomFileResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-1005")
+    def "can handle self referencing dependency"() {
+        given:
+        file("settings.gradle") << "include 'client'"
+
+        and:
+        mavenRepo().module('group', 'artifact', '1.0').dependsOn('group', 'artifact', '1.0').publish()
+
+        and:
+        buildFile << """
+            repositories {
+                maven { url "${mavenRepo().uri}" }
+            }
+            configurations { compile }
+            dependencies {
+                compile "group:artifact:1.0"
+            }
+            task libs << { assert configurations.compile.files.collect {it.name} == ['artifact-1.0.jar'] }
+        """
+
+        expect:
+        succeeds ":libs"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
new file mode 100644
index 0000000..e5b1d7a
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenDynamicResolveIntegrationTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenDynamicResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "can resolve dynamic version declared in pom as transitive dependency from HTTP Maven repository"() {
+        given:
+        server.start()
+
+        mavenRepo().module('group', 'projectC', '1.1').publish()
+        def projectC = mavenRepo().module('group', 'projectC', '1.5').publish()
+        mavenRepo().module('group', 'projectC', '2.0').publish()
+        def projectB = mavenRepo().module('group', 'projectB').dependsOn("group",'projectC', '[1.0, 2.0)').publish()
+        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+
+        buildFile << """
+    repositories {
+        maven { url 'http://localhost:${server.port}/repo1' }
+    }
+    configurations { compile }
+    dependencies {
+        compile 'group:projectA:1.0'
+    }
+
+    task retrieve(type: Sync) {
+        into 'libs'
+        from configurations.compile
+    }
+    """
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+        server.expectGet('/repo1/group/projectC/maven-metadata.xml', projectC.rootMetaDataFile)
+        server.expectGet('/repo1/group/projectC/1.5/projectC-1.5.pom', projectC.pomFile)
+        server.expectGet('/repo1/group/projectC/1.5/projectC-1.5.jar', projectC.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar', 'projectC-1.5.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..7ef9f59
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenFileRepoResolveIntegrationTest.groovy
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenFileRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    public void "can resolve snapshots uncached from local Maven repository"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        and:
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenRepo().uri}" } }
+dependencies { compile 'group:projectA:1.2-SNAPSHOT' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants(moduleA.artifactFile.name)
+        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9.1')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        buildDir.assertHasDescendants(moduleA.artifactFile.name, 'projectB-9.1.jar')
+        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+
+    public void "does not cache artifacts and metadata from local Maven repository"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        and:
+        buildFile << """
+configurations { compile }
+repositories { maven { url "${mavenRepo().uri}" } }
+dependencies { compile 'group:projectA:1.2' }
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants('projectA-1.2.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
+
+        when:
+        moduleA.dependsOn('group', 'projectB', '9.1')
+        moduleA.publishWithChangedContent()
+        run 'retrieve'
+
+        then:
+        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+
+    public void "uses artifactUrls to resolve artifacts"() {
+        given:
+        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
+        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
+        moduleA.publish()
+        moduleB.publish()
+
+        def artifactsRepo = new MavenRepository(distribution.testFile('artifactsRepo'));
+        // Create a module to get the correct module directory, but do not publish the module
+        def artifactsModuleA = artifactsRepo.module('group', 'projectA', '1.2')
+        moduleA.artifactFile.moveToDirectory(artifactsModuleA.moduleDir)
+
+        and:
+        buildFile << """
+repositories {
+    maven {
+        url "${mavenRepo().uri}"
+        artifactUrls "${artifactsRepo.uri}"
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.2'
+    compile 'group:projectB:9.1'
+}
+
+task retrieve(type: Sync) {
+    from configurations.compile
+    into 'build'
+}
+"""
+
+        when:
+        run 'retrieve'
+
+        then:
+        def buildDir = file('build')
+        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
+        buildDir.file('projectA-1.2.jar').assertIsCopyOf(artifactsModuleA.artifactFile)
+        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..af069ee
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import org.gradle.util.TestFile
+import org.junit.Rule
+
+class MavenHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    @Rule public final TestResources resources = new TestResources();
+
+    def canResolveDependenciesFromMultipleMavenRepositories() {
+        given:
+        List expectedFiles = ['sillyexceptions-1.0.1.jar', 'repotest-1.0.jar', 'testdep-1.0.jar', 'testdep2-1.0.jar',
+                'classifier-1.0-jdk15.jar', 'classifier-dep-1.0.jar', 'jaronly-1.0.jar']
+
+        File projectDir = distribution.testDir
+        executer.inDirectory(projectDir).withTasks('retrieve').run()
+        
+        expect:
+        expectedFiles.each { new TestFile(projectDir, 'build', it).assertExists() }
+    }
+
+    def "can resolve and cache dependencies from HTTP Maven repository"() {
+        given:
+        server.start()
+
+        def projectB = mavenRepo().module('group', 'projectB').publish()
+        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0'
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        and:
+        run 'retrieve'
+        
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+        
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+        
+        then:
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "can resolve and cache artifact-only dependencies from an HTTP Maven repository"() {
+        server.start()
+        given:
+        def module = mavenRepo().module('group', 'projectA', '1.2')
+        module.publish()
+
+        and:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo1" }
+    maven { url "http://localhost:${server.port}/repo2" }
+}
+configurations { compile }
+dependencies { compile 'group:projectA:1.2 at jar' }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
+}
+"""
+
+        when:
+        // TODO: Should meta-data be fetched for an artifact-only dependency?
+        server.expectGetMissing('/repo1/group/projectA/1.2/projectA-1.2.pom')
+        server.expectHeadMissing('/repo1/group/projectA/1.2/projectA-1.2.jar')
+
+        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.pom', module.pomFile)
+        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.jar', module.artifactFile)
+
+        then:
+        succeeds('listJars')
+
+        when:
+        server.resetExpectations()
+        // No extra calls for cached dependencies
+
+        then:
+        succeeds('listJars')
+    }
+
+    def "does not download source and javadoc artifacts from HTTP Maven repository until required"() {
+        given:
+        server.start()
+
+        def projectA = mavenRepo().module('group', 'projectA', '1.0')
+        projectA.artifact(classifier: 'sources')
+        projectA.artifact(classifier: 'javadoc')
+        projectA.publish()
+        def sourceJar = projectA.artifactFile(classifier: 'sources')
+        def javadocJar = projectA.artifactFile(classifier: 'javadoc')
+
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+dependencies {
+    compile 'group:projectA:1.0'
+}
+eclipse { classpath { downloadJavadoc = true } }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-sources.jar', sourceJar)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar', javadocJar)
+
+        then:
+        succeeds 'eclipseClasspath'
+    }
+
+    def "only attempts to download missing artifacts from HTTP Maven repository once"() {
+        given:
+        server.start()
+
+        def projectA = mavenRepo().module('group', 'projectA', '1.0')
+        projectA.publish()
+
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'eclipse'
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+dependencies {
+    compile 'group:projectA:1.0'
+}
+eclipse { classpath { downloadJavadoc = true } }
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
+}
+"""
+
+        when:
+        server.resetExpectations()
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-sources.jar')
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar')
+
+        then:
+        succeeds 'eclipseClasspath'
+
+        when:
+        server.resetExpectations()
+
+        then:
+        succeeds 'eclipseClasspath'
+    }
+
+    def "can resolve and cache dependencies from multiple HTTP Maven repositories"() {
+        given:
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url 'http://localhost:${server.port}/repo2' }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
+}
+"""
+
+        def projectA = mavenRepo().module('group', 'projectA').publish()
+        def projectB = mavenRepo().module('group', 'projectB').publish()
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+
+        // Looks for POM and JAR in repo1 before looking in repo2 (jar is an attempt to handle publication without module descriptor)
+        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.pom')
+        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeeds 'listJars'
+
+        when:
+        server.resetExpectations()
+        // No server requests when all jars cached
+
+        then:
+        succeeds 'listJars'
+    }
+
+    def "uses artifactsUrl to resolve artifacts"() {
+        given:
+        server.start()
+
+        buildFile << """
+repositories {
+    maven {
+        url 'http://localhost:${server.port}/repo1'
+        artifactUrls 'http://localhost:${server.port}/repo2'
+    }
+}
+configurations { compile }
+dependencies {
+    compile 'group:projectA:1.0', 'group:projectB:1.0'
+}
+task listJars << {
+    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
+}
+"""
+
+        def projectA = mavenRepo().module('group', 'projectA')
+        def projectB = mavenRepo().module('group', 'projectB')
+        projectA.publish()
+        projectB.publish()
+
+        when:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
+        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+        then:
+        succeeds 'listJars'
+    }
+
+    def "can resolve and cache dependencies from HTTP Maven repository with invalid settings.xml"() {
+            given:
+            server.start()
+
+            def projectB = mavenRepo().module('group', 'projectB').publish()
+            def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
+
+            buildFile << """
+    repositories {
+        maven { url 'http://localhost:${server.port}/repo1' }
+    }
+    configurations { compile }
+    dependencies {
+        compile 'group:projectA:1.0'
+    }
+
+    task retrieve(type: Sync) {
+        into 'libs'
+        from configurations.compile
+    }
+    """
+
+            def m2Home = file("M2_REPO")
+            def settingsFile = m2Home.file("conf/settings.xml")
+            settingsFile << "invalid content... blabla"
+
+            when:
+            server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+            server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+            server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
+            server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
+
+            and:
+
+            executer.withEnvironmentVars(M2_HOME:m2Home.absolutePath)
+            run 'retrieve'
+
+            then:
+            file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
+            def snapshot = file('libs/projectA-1.0.jar').snapshot()
+
+            when:
+            server.resetExpectations()
+            and:
+            run 'retrieve'
+
+            then:
+            file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+        }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index daa0c74..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.fixtures.MavenRepository
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenLocalDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    public void "can resolve snapshots uncached from local Maven repository"() {
-        given:
-        def moduleA = mavenRepo().module('group', 'projectA', '1.2-SNAPSHOT')
-        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
-        moduleA.publish()
-        moduleB.publish()
-
-        and:
-        buildFile << """
-configurations { compile }
-repositories { maven { url "${mavenRepo().uri}" } }
-dependencies { compile 'group:projectA:1.2-SNAPSHOT' }
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'build'
-}
-"""
-
-        when:
-        run 'retrieve'
-
-        then:
-        def buildDir = file('build')
-        buildDir.assertHasDescendants(moduleA.artifactFile.name)
-        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
-
-        when:
-        moduleA.dependsOn('group', 'projectB', '9.1')
-        moduleA.publishWithChangedContent()
-        run 'retrieve'
-
-        then:
-        buildDir.assertHasDescendants(moduleA.artifactFile.name, 'projectB-9.1.jar')
-        buildDir.file(moduleA.artifactFile.name).assertIsCopyOf(moduleA.artifactFile)
-        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
-    }
-
-    public void "does not cache artifacts and metadata from local Maven repository"() {
-        given:
-        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
-        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
-        moduleA.publish()
-        moduleB.publish()
-
-        and:
-        buildFile << """
-configurations { compile }
-repositories { maven { url "${mavenRepo().uri}" } }
-dependencies { compile 'group:projectA:1.2' }
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'build'
-}
-"""
-
-        when:
-        run 'retrieve'
-
-        then:
-        def buildDir = file('build')
-        buildDir.assertHasDescendants('projectA-1.2.jar')
-        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
-
-        when:
-        moduleA.dependsOn('group', 'projectB', '9.1')
-        moduleA.publishWithChangedContent()
-        run 'retrieve'
-
-        then:
-        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
-        buildDir.file('projectA-1.2.jar').assertIsCopyOf(moduleA.artifactFile)
-        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
-    }
-
-    public void "uses artifactUrls to resolve artifacts"() {
-        given:
-        def moduleA = mavenRepo().module('group', 'projectA', '1.2')
-        def moduleB = mavenRepo().module('group', 'projectB', '9.1')
-        moduleA.publish()
-        moduleB.publish()
-
-        def artifactsRepo = new MavenRepository(distribution.testFile('artifactsRepo'));
-        // Create a module to get the correct module directory, but do not publish the module
-        def artifactsModuleA = artifactsRepo.module('group', 'projectA', '1.2')
-        moduleA.artifactFile.moveToDirectory(artifactsModuleA.moduleDir)
-
-        and:
-        buildFile << """
-repositories {
-    maven {
-        url "${mavenRepo().uri}"
-        artifactUrls "${artifactsRepo.uri}"
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-    compile 'group:projectB:9.1'
-}
-
-task retrieve(type: Sync) {
-    from configurations.compile
-    into 'build'
-}
-"""
-
-        when:
-        run 'retrieve'
-
-        then:
-        def buildDir = file('build')
-        buildDir.assertHasDescendants('projectA-1.2.jar', 'projectB-9.1.jar')
-        buildDir.file('projectA-1.2.jar').assertIsCopyOf(artifactsModuleA.artifactFile)
-        buildDir.file('projectB-9.1.jar').assertIsCopyOf(moduleB.artifactFile)
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
new file mode 100644
index 0000000..344bfab
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixture.M2Installation
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.MavenModule
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TestFile
+import org.junit.Rule
+
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.not
+
+class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule SetSystemProperties sysProp = new SetSystemProperties()
+
+    public void setup() {
+        requireOwnUserHomeDir()
+        buildFile << """
+                repositories {
+                    mavenLocal()
+                }
+                configurations { compile }
+                dependencies {
+                    compile 'group:projectA:1.2'
+                }
+
+                task retrieve(type: Sync) {
+                    from configurations.compile
+                    into 'build'
+                }"""
+    }
+
+    public void "can resolve artifacts from local m2 with not existing user settings.xml"() {
+        given:
+        def m2 = localM2()
+        def moduleA = m2.mavenRepo().module('group', 'projectA', '1.2')
+        moduleA.publish()
+        and:
+        withM2(m2)
+
+        when:
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+
+    }
+
+    public void "can resolve artifacts from local m2 with custom localRepository defined in user settings.xml"() {
+        given:
+        def artifactRepo = new MavenRepository(file("artifactrepo"))
+        def m2 = localM2() {
+            userSettingsFile << """<settings>
+                        <localRepository>${artifactRepo.rootDir.absolutePath}</localRepository>
+                    </settings>"""
+        }
+        def moduleA = artifactRepo.module('group', 'projectA', '1.2')
+        moduleA.publish()
+
+        when:
+        withM2(m2)
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+    }
+
+    public void "can resolve artifacts from local m2 with custom localRepository defined in global settings.xml"() {
+        given:
+        def artifactRepo = new MavenRepository(file("artifactrepo"))
+        def m2 = localM2() {
+            createGlobalSettingsFile(file("global_M2")) << """<settings>
+                        <localRepository>${artifactRepo.rootDir.absolutePath}</localRepository>
+                    </settings>"""
+        }
+
+        def moduleA = artifactRepo.module('group', 'projectA', '1.2')
+        moduleA.publish()
+
+        when:
+        withM2(m2)
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+    }
+
+    public void "localRepository in user settings take precedence over the localRepository global settings"() {
+        given:
+        def globalRepo = new MavenRepository(file("globalArtifactRepo"))
+        def userRepo = new MavenRepository(file("userArtifactRepo"))
+        def m2 = localM2() {
+            createGlobalSettingsFile(file("global_M2")) << """<settings>
+                            <localRepository>${globalRepo.rootDir.absolutePath}</localRepository>
+                        </settings>"""
+            userSettingsFile << """<settings>
+                                    <localRepository>${userRepo.rootDir.absolutePath}</localRepository>
+                                </settings>"""
+
+        }
+
+        def moduleA = userRepo.module('group', 'projectA', '1.2')
+        moduleA.publish()
+
+        def globalModuleA = globalRepo.module('group', 'projectA', '1.2')
+        globalModuleA.publishWithChangedContent() // to ensure that resulting artifact
+                                                  // has different hash than userModuleA.artifactFile
+
+        when:
+        withM2(m2)
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+    }
+
+    public void "fail with meaningful error message if settings.xml is invalid"() {
+        given:
+        def m2 = localM2() {
+            userSettingsFile << "invalid content"
+        }
+
+        when:
+        withM2(m2)
+        def failure = runAndFail('retrieve')
+
+        then:
+        failure.assertThatCause(containsString(String.format("Non-parseable settings %s:", m2.userSettingsFile.absolutePath)));
+    }
+
+    public void "mavenLocal is ignored if no local maven repository exists"() {
+        given:
+        def anotherRepo = new MavenRepository(file("another-local-repo"))
+        def moduleA = anotherRepo.module('group', 'projectA', '1.2')
+        moduleA.publishWithChangedContent();
+
+        when:
+        buildFile << """
+        repositories{
+            maven { url "${anotherRepo.uri}" }
+        }
+        """
+
+        run 'retrieve'
+
+        then:
+        hasArtifact(moduleA)
+    }
+
+    def hasArtifact(MavenModule module) {
+        def buildDir = file('build')
+        def artifactName = module.artifactFile.getName()
+        buildDir.assertHasDescendants(artifactName)
+        buildDir.file(artifactName).assertIsCopyOf(module.artifactFile)
+    }
+
+
+    def withM2(M2Installation m2) {
+        def args = ["-Duser.home=${m2.userM2Directory.parentFile.absolutePath}".toString()]
+        if (m2.globalMavenDirectory?.exists()) {
+            executer.withEnvironmentVars(M2_HOME:m2.globalMavenDirectory.absolutePath)
+        }
+        executer.withArguments(args)
+    }
+
+    M2Installation localM2() {
+        TestFile testUserHomeDir = distribution.getUserHomeDir()
+        TestFile userM2Dir = testUserHomeDir.file(".m2")
+        new M2Installation(userM2Dir)
+    }
+
+    M2Installation localM2(Closure configClosure) {
+        M2Installation m2 = localM2()
+        configClosure.setDelegate(m2)
+        configClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
+        configClosure.call()
+        m2
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
new file mode 100644
index 0000000..43d5010
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenParentPomResolveIntegrationTest.groovy
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2011 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 org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenParentPomResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "includes dependencies from parent pom"() {
+        given:
+        server.start()
+
+        def parentDep = mavenRepo().module("org", "parent_dep").publish()
+        def childDep = mavenRepo().module("org", "child_dep").publish()
+
+        def parent = mavenRepo().module("org", "parent")
+        parent.type = 'pom'
+        parent.dependsOn("parent_dep")
+        parent.publish()
+
+        def child = mavenRepo().module("org", "child")
+        child.dependsOn("child_dep")
+        child.parentPomSection = """
+<parent>
+  <groupId>org</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0</version>
+</parent>
+"""
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
+        server.expectGet('/repo1/org/parent/1.0/parent-1.0.pom', parent.pomFile)
+
+        // Will always check for a default artifact with a module with 'pom' packaging
+        server.expectHeadMissing('/repo1/org/parent/1.0/parent-1.0.jar')
+
+        server.expectGet('/repo1/org/child/1.0/child-1.0.jar', child.artifactFile)
+
+        server.expectGet('/repo1/org/parent_dep/1.0/parent_dep-1.0.pom', parentDep.pomFile)
+        server.expectGet('/repo1/org/parent_dep/1.0/parent_dep-1.0.jar', parentDep.artifactFile)
+        server.expectGet('/repo1/org/child_dep/1.0/child_dep-1.0.pom', childDep.pomFile)
+        server.expectGet('/repo1/org/child_dep/1.0/child_dep-1.0.jar', childDep.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar', 'parent_dep-1.0.jar', 'child_dep-1.0.jar')
+    }
+
+    def "looks for parent pom in different repository"() {
+        given:
+        server.start()
+
+        def parent = mavenRepo().module("org", "parent")
+        parent.type = 'pom'
+        parent.publish()
+
+        def child = mavenRepo().module("org", "child")
+        child.parentPomSection = """
+<parent>
+  <groupId>org</groupId>
+  <artifactId>parent</artifactId>
+  <version>1.0</version>
+</parent>
+"""
+        child.publish()
+
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url 'http://localhost:${server.port}/repo2' }
+}
+configurations { compile }
+dependencies { compile 'org:child:1.0' }
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when:
+        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
+        server.expectGet('/repo1/org/child/1.0/child-1.0.jar', child.artifactFile)
+
+        server.expectGetMissing('/repo1/org/parent/1.0/parent-1.0.pom')
+        server.expectHeadMissing('/repo1/org/parent/1.0/parent-1.0.jar')
+        server.expectGet('/repo2/org/parent/1.0/parent-1.0.pom', parent.pomFile)
+        server.expectHead('/repo2/org/parent/1.0/parent-1.0.jar', parent.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('child-1.0.jar')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
new file mode 100644
index 0000000..af15b46
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenPomPackagingResolveIntegrationTest.groovy
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+import spock.lang.Issue
+import org.gradle.integtests.fixtures.MavenModule
+import spock.lang.FailsWith
+
+class MavenPomPackagingResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    MavenModule projectA
+
+    def setup() {
+        server.start()
+
+        projectA = mavenRepo().module('group', 'projectA')
+    }
+
+    private void buildWithDependencies(def dependencies) {
+        buildFile << """
+repositories {
+    maven { url 'http://localhost:${server.port}/repo1' }
+    maven { url 'http://localhost:${server.port}/repo2' }
+}
+configurations { compile }
+dependencies {
+    $dependencies
+}
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+    }
+
+    def "looks for jar artifact for pom with packaging of type 'pom' in the same repository only"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging('pom')
+
+        and:
+        // First attempts to resolve in repo1
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.pom')
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
+
+        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHead('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+        def snapshot = file('libs/projectA-1.0.jar').snapshot()
+
+        when:
+        server.resetExpectations()
+        and:
+        run 'retrieve'
+
+        then: // Uses cached artifacts
+        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
+    }
+
+    def "will use jar artifact for pom with packaging that maps to jar"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging(packaging)
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+        file('libs/projectA-1.0.jar').assertIsCopyOf(projectA.artifactFile)
+
+        where:
+        packaging << ['', 'jar', 'eclipse-plugin', 'bundle']
+    }
+
+
+    @Issue('GRADLE-2188')
+    def "will use jar artifact for pom with packaging 'orbit'"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging('orbit')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.orbit')
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.jar')
+        file('libs/projectA-1.0.jar').assertIsCopyOf(projectA.artifactFile)
+    }
+
+    @Issue('GRADLE-2188')
+    def "where 'module.custom' exists, will use it as main artifact for pom with packaging 'custom' and emit deprecation warning"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging('custom', 'custom')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHead('/repo1/group/projectA/1.0/projectA-1.0.custom', projectA.artifactFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.custom', projectA.artifactFile)
+
+        and:
+        executer.withDeprecationChecksDisabled()
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.custom')
+        file('libs/projectA-1.0.custom').assertIsCopyOf(projectA.artifactFile)
+
+        and:
+        result.output.contains("Deprecated: relying on packaging to define the extension of the main artifact is deprecated")
+    }
+
+    def "fails and reports type-based location if neither packaging-based or type-based artifact can be located"() {
+        when:
+        buildWithDependencies("compile 'group:projectA:1.0'")
+        publishWithPackaging('custom')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
+        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
+
+        then:
+        fails 'retrieve'
+
+        and:
+        result.error.contains("Artifact 'group:projectA:1.0 at jar' not found.")
+    }
+
+    def "will use non-jar dependency type to determine jar artifact location"() {
+        when:
+        buildWithDependencies("""
+compile('group:projectA:1.0') {
+    artifact {
+        name = 'projectA'
+        type = 'zip'
+    }
+}
+""")
+        publishWithPackaging('custom')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+
+        // TODO:GRADLE-2188 This call should not be required, since "type='zip'" on the dependency alleviates the need to check for the packaging artifact
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.zip')
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+    }
+
+    def "will use non-jar maven dependency type to determine artifact location"() {
+        when:
+        buildWithDependencies("""
+compile 'group:mavenProject:1.0'
+""")
+        def mavenProject = mavenRepo().module('group', 'mavenProject', '1.0').hasType('pom').dependsOn('group', 'projectA', '1.0', 'zip').publish()
+        publishWithPackaging('custom', 'zip')
+
+        and:
+        server.expectGet('/repo1/group/mavenProject/1.0/mavenProject-1.0.pom', mavenProject.pomFile)
+        server.expectHeadMissing('/repo1/group/mavenProject/1.0/mavenProject-1.0.jar')
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+
+        // TODO:GRADLE-2188 This call should not be required, since "type='zip'" on the dependency alleviates the need to check for the packaging artifact
+        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.custom')
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.zip')
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+    }
+
+    @FailsWith(value = AssertionError, reason = "Pending better fix for GRADLE-2188")
+    def "does not emit deprecation warning if dependency type is used to locate artifact, even if custom packaging matches file extension"() {
+        when:
+        buildWithDependencies("""
+compile('group:projectA:1.0') {
+    artifact {
+        name = 'projectA'
+        type = 'zip'
+    }
+}
+""")
+        publishWithPackaging('zip')
+
+        and:
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
+        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.zip', projectA.artifactFile)
+
+        then:
+        succeeds 'retrieve'
+
+        and:
+        file('libs').assertHasDescendants('projectA-1.0.zip')
+        file('libs/projectA-1.0.zip').assertIsCopyOf(projectA.artifactFile)
+
+        and: "Stop the http server here to allow failure to be declared (otherwise occurs in tearDown) - remove this when the test is fixed"
+        server.stop()
+    }
+
+    private def publishWithPackaging(String packaging, String type = 'jar') {
+        projectA.packaging = packaging
+        projectA.type = type
+        projectA.publish()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index 4301f3f..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.fixtures.TestResources
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-import org.gradle.util.TestFile
-import org.junit.Rule
-
-class MavenRemoteDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule public final TestResources resources = new TestResources();
-\
-    def canResolveDependenciesFromMultipleMavenRepositories() {
-        given:
-        List expectedFiles = ['sillyexceptions-1.0.1.jar', 'repotest-1.0.jar', 'testdep-1.0.jar', 'testdep2-1.0.jar',
-                'classifier-1.0-jdk15.jar', 'classifier-dep-1.0.jar', 'jaronly-1.0.jar']
-
-        File projectDir = distribution.testDir
-        executer.inDirectory(projectDir).withTasks('retrieve').run()
-        
-        expect:
-        expectedFiles.each { new TestFile(projectDir, 'build', it).assertExists() }
-    }
-
-    def "can resolve and cache dependencies from HTTP Maven repository"() {
-        given:
-        server.start()
-
-        def projectB = mavenRepo().module('group', 'projectB').publish()
-        def projectA = mavenRepo().module('group', 'projectA').dependsOn('projectB').publish()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0'
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
-
-        and:
-        run 'retrieve'
-        
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar', 'projectB-1.0.jar')
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
-        
-        when:
-        server.resetExpectations()
-        and:
-        run 'retrieve'
-        
-        then:
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-    }
-
-    def "can resolve and cache artifact-only dependencies from an HTTP Maven repository"() {
-        server.start()
-        given:
-        def module = mavenRepo().module('group', 'projectA', '1.2')
-        module.publish()
-
-        and:
-        buildFile << """
-repositories {
-    maven { url "http://localhost:${server.port}/repo1" }
-    maven { url "http://localhost:${server.port}/repo2" }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.2 at jar' }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.2.jar']
-}
-"""
-
-        when:
-        // TODO: Should meta-data be fetched for an artifact-only dependency?
-        server.expectGetMissing('/repo1/group/projectA/1.2/projectA-1.2.pom')
-        server.expectHeadMissing('/repo1/group/projectA/1.2/projectA-1.2.jar')
-
-        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.pom', module.pomFile)
-        server.expectGet('/repo2/group/projectA/1.2/projectA-1.2.jar', module.artifactFile)
-
-        then:
-        succeeds('listJars')
-
-        when:
-        server.resetExpectations()
-        // No extra calls for cached dependencies
-
-        then:
-        succeeds('listJars')
-    }
-
-    def "does not download source and javadoc artifacts from HTTP Maven repository until required"() {
-        given:
-        server.start()
-
-        def projectA = mavenRepo().module('group', 'projectA', '1.0')
-        projectA.artifact(classifier: 'sources')
-        projectA.artifact(classifier: 'javadoc')
-        projectA.publish()
-        def sourceJar = projectA.artifactFile(classifier: 'sources')
-        def javadocJar = projectA.artifactFile(classifier: 'javadoc')
-
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'eclipse'
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-dependencies {
-    compile 'group:projectA:1.0'
-}
-eclipse { classpath { downloadJavadoc = true } }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-}
-"""
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        then:
-        succeeds 'listJars'
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-sources.jar', sourceJar)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar', javadocJar)
-
-        then:
-        succeeds 'eclipseClasspath'
-    }
-
-    def "only attempts to download missing artifacts from HTTP Maven repository once"() {
-        given:
-        server.start()
-
-        def projectA = mavenRepo().module('group', 'projectA', '1.0')
-        projectA.publish()
-
-        buildFile << """
-apply plugin: 'java'
-apply plugin: 'eclipse'
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-dependencies {
-    compile 'group:projectA:1.0'
-}
-eclipse { classpath { downloadJavadoc = true } }
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar']
-}
-"""
-
-        when:
-        server.resetExpectations()
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-sources.jar')
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0-javadoc.jar')
-
-        then:
-        succeeds 'eclipseClasspath'
-
-        when:
-        server.resetExpectations()
-
-        then:
-        succeeds 'eclipseClasspath'
-    }
-
-    def "can resolve and cache dependencies from multiple HTTP Maven repositories"() {
-        given:
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-    maven { url 'http://localhost:${server.port}/repo2' }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0', 'group:projectB:1.0'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
-}
-"""
-
-        def projectA = mavenRepo().module('group', 'projectA').publish()
-        def projectB = mavenRepo().module('group', 'projectB').publish()
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-
-        // Looks for POM and JAR in repo1 before looking in repo2 (jar is an attempt to handle publication without module descriptor)
-        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.pom')
-        server.expectHeadMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
-
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
-
-        then:
-        succeeds 'listJars'
-
-        when:
-        server.resetExpectations()
-        // No server requests when all jars cached
-
-        then:
-        succeeds 'listJars'
-    }
-
-    def "uses artifactsUrl to resolve artifacts"() {
-        given:
-        server.start()
-
-        buildFile << """
-repositories {
-    maven {
-        url 'http://localhost:${server.port}/repo1'
-        artifactUrls 'http://localhost:${server.port}/repo2'
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.0', 'group:projectB:1.0'
-}
-task listJars << {
-    assert configurations.compile.collect { it.name } == ['projectA-1.0.jar', 'projectB-1.0.jar']
-}
-"""
-
-        def projectA = mavenRepo().module('group', 'projectA')
-        def projectB = mavenRepo().module('group', 'projectB')
-        projectA.publish()
-        projectB.publish()
-
-        when:
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectGet('/repo1/group/projectB/1.0/projectB-1.0.pom', projectB.pomFile)
-
-        server.expectGet('/repo1/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGetMissing('/repo1/group/projectB/1.0/projectB-1.0.jar')
-        server.expectGet('/repo2/group/projectB/1.0/projectB-1.0.jar', projectB.artifactFile)
-
-        then:
-        succeeds 'listJars'
-    }
-
-    def "can resolve dependencies from password protected HTTP Maven repository"() {
-        given:
-        server.start()
-        
-        buildFile << """
-repositories {
-    maven {
-        url "http://localhost:${server.port}/repo"
-
-        credentials {
-            password 'password'
-            username 'username'
-        }
-    }
-}
-configurations { compile }
-dependencies {
-    compile 'group:projectA:1.2'
-}
-task retrieve(type: Sync) {
-    into 'build'
-    from configurations.compile
-}
-"""
-        and:
-        def module = mavenRepo().module('group', 'projectA', '1.2')
-        module.publish()
-
-        when:
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.pom', 'username', 'password', module.pomFile)
-        server.expectGet('/repo/group/projectA/1.2/projectA-1.2.jar', 'username', 'password', module.artifactFile)
-
-        and:
-        run 'retrieve'
-        
-        then:
-        file('build').assertHasDescendants('projectA-1.2.jar')
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemotePomResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemotePomResolutionIntegrationTest.groovy
deleted file mode 100644
index 18a9895..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemotePomResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenRemotePomResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "looks for jar artifact for pom with packing of type 'pom' in the same repository only"() {
-        given:
-        server.start()
-
-        def projectA = mavenRepo().module('group', 'projectA')
-        projectA.type = 'pom'
-        projectA.publish()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-    maven { url 'http://localhost:${server.port}/repo2' }
-}
-configurations { compile }
-dependencies { compile 'group:projectA:1.0' }
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when:
-        // First attempts to resolve in repo1
-        server.expectGetMissing('/repo1/group/projectA/1.0/projectA-1.0.pom')
-        server.expectHeadMissing('/repo1/group/projectA/1.0/projectA-1.0.jar')
-
-        // Then resolves pom (with 'pom' packaging) in repo2, looks there for jar as well
-        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.pom', projectA.pomFile)
-        server.expectHead('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-        server.expectGet('/repo2/group/projectA/1.0/projectA-1.0.jar', projectA.artifactFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('projectA-1.0.jar')
-        def snapshot = file('libs/projectA-1.0.jar').snapshot()
-
-        when:
-        server.resetExpectations()
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs/projectA-1.0.jar').assertHasNotChangedSince(snapshot)
-    }
-
-    def "includes dependencies from parent pom"() {
-        given:
-        server.start()
-
-        def parentDep = mavenRepo().module("org", "parent_dep").publish()
-        def childDep = mavenRepo().module("org", "child_dep").publish()
-
-        def parent = mavenRepo().module("org", "parent")
-        parent.type = 'pom'
-        parent.dependsOn("parent_dep")
-        parent.publish()
-
-        def child = mavenRepo().module("org", "child")
-        child.dependsOn("child_dep")
-        child.parentPomSection = """
-<parent>
-  <groupId>org</groupId>
-  <artifactId>parent</artifactId>
-  <version>1.0</version>
-</parent>
-"""
-        child.publish()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-}
-configurations { compile }
-dependencies { compile 'org:child:1.0' }
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when:
-        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
-        server.expectGet('/repo1/org/parent/1.0/parent-1.0.pom', parent.pomFile)
-
-        // Will always check for a default artifact with a module with 'pom' packaging
-        server.expectHeadMissing('/repo1/org/parent/1.0/parent-1.0.jar')
-
-        server.expectGet('/repo1/org/child/1.0/child-1.0.jar', child.artifactFile)
-
-        server.expectGet('/repo1/org/parent_dep/1.0/parent_dep-1.0.pom', parentDep.pomFile)
-        server.expectGet('/repo1/org/parent_dep/1.0/parent_dep-1.0.jar', parentDep.artifactFile)
-        server.expectGet('/repo1/org/child_dep/1.0/child_dep-1.0.pom', childDep.pomFile)
-        server.expectGet('/repo1/org/child_dep/1.0/child_dep-1.0.jar', childDep.artifactFile)
-
-        // TODO: These shouldn't be required. Or at most should be HEAD requests
-        server.expectGet('/repo1/org/parent/1.0/parent-1.0.pom', parent.pomFile)
-        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('child-1.0.jar', 'parent_dep-1.0.jar', 'child_dep-1.0.jar')
-    }
-
-    def "looks for parent pom in different repository"() {
-        given:
-        server.start()
-
-        def parent = mavenRepo().module("org", "parent")
-        parent.type = 'pom'
-        parent.publish()
-
-        def child = mavenRepo().module("org", "child")
-        child.parentPomSection = """
-<parent>
-  <groupId>org</groupId>
-  <artifactId>parent</artifactId>
-  <version>1.0</version>
-</parent>
-"""
-        child.publish()
-
-        buildFile << """
-repositories {
-    maven { url 'http://localhost:${server.port}/repo1' }
-    maven { url 'http://localhost:${server.port}/repo2' }
-}
-configurations { compile }
-dependencies { compile 'org:child:1.0' }
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when:
-        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
-        server.expectGet('/repo1/org/child/1.0/child-1.0.jar', child.artifactFile)
-
-        server.expectGetMissing('/repo1/org/parent/1.0/parent-1.0.pom')
-        server.expectHeadMissing('/repo1/org/parent/1.0/parent-1.0.jar')
-        server.expectGet('/repo2/org/parent/1.0/parent-1.0.pom', parent.pomFile)
-        server.expectHead('/repo2/org/parent/1.0/parent-1.0.jar', parent.artifactFile)
-
-        // TODO: These shouldn't be required. Or at most should be HEAD requests
-        server.expectGet('/repo1/org/child/1.0/child-1.0.pom', child.pomFile)
-        server.expectGet('/repo2/org/parent/1.0/parent-1.0.pom', parent.pomFile)
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('child-1.0.jar')
-    }
-}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotRemoteDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotRemoteDependencyResolutionIntegrationTest.groovy
deleted file mode 100644
index 58f6719..0000000
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotRemoteDependencyResolutionIntegrationTest.groovy
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.integtests.resolve.maven
-
-import org.gradle.integtests.fixtures.MavenModule
-import org.gradle.integtests.fixtures.MavenRepository
-import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
-
-class MavenSnapshotRemoteDependencyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-
-    def "can find and cache snapshots in multiple Maven HTTP repositories"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    maven { url "http://localhost:${server.port}/repo1" }
-    maven { url "http://localhost:${server.port}/repo2" }
-}
-
-configurations { compile }
-
-dependencies {
-    compile "org.gradle:projectA:1.0-SNAPSHOT"
-    compile "org.gradle:projectB:1.0-SNAPSHOT"
-    compile "org.gradle:nonunique:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        and: "snapshot modules are published"
-        def projectA = mavenRepo().module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
-        def projectB = mavenRepo().module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
-        def nonUnique = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
-
-        when: "Server provides projectA from repo1"
-        expectModuleServed(projectA, '/repo1')
-
-        and: "Server provides projectB from repo2"
-        expectModuleMissing(projectB, '/repo1')
-        expectModuleServed(projectB, '/repo2')
-
-        and: "Server provides nonunique snapshot from repo2"
-        expectModuleMissing(nonUnique, '/repo1')
-        expectModuleServed(nonUnique, '/repo2')
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then: "Snapshots are downloaded"
-        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
-        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
-        def snapshotNonUnique = file('libs/nonunique-1.0-SNAPSHOT.jar').snapshot()
-
-        when: "We resolve with snapshots cached: no server requests"
-        server.resetExpectations()
-        def result = run('retrieve')
-
-        then: "Everything is up to date"
-        result.assertTaskSkipped(':retrieve')
-        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
-        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotNonUnique);
-    }
-
-    def "can find and cache snapshots in Maven HTTP repository with additional artifact urls"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    maven {
-        url "http://localhost:${server.port}/repo1"
-        artifactUrls "http://localhost:${server.port}/repo2"
-    }
-}
-
-configurations { compile }
-
-dependencies {
-    compile "org.gradle:projectA:1.0-SNAPSHOT"
-    compile "org.gradle:projectB:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        and: "snapshot modules are published"
-        def projectA = mavenRepo().module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
-        def projectB = mavenRepo().module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
-
-        when: "Server provides projectA from repo1"
-        expectModuleServed(projectA, '/repo1')
-
-        and: "Server provides projectB with artifact in repo2"
-        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/maven-metadata.xml", projectB.moduleDir.file("maven-metadata.xml"))
-        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/${projectB.pomFile.name}", projectB.pomFile)
-        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/maven-metadata.xml", projectB.moduleDir.file("maven-metadata.xml"))
-        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/${projectB.artifactFile.name}")
-        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar")
-
-        // TODO: This is not correct - should be looking for jar with unique version to fetch snapshot
-        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar", projectB.artifactFile)
-//        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/${projectB.artifactFile.name}",  projectB.artifactFile)
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then: "Snapshots are downloaded"
-        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar')
-        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
-        def snapshotB = file('libs/projectB-1.0-SNAPSHOT.jar').snapshot()
-
-        when: "We resolve with snapshots cached: no server requests"
-        server.resetExpectations()
-        def result = run('retrieve')
-
-        then: "Everything is up to date"
-        result.assertTaskSkipped(':retrieve')
-        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
-        file('libs/projectB-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotB);
-    }
-
-    def "will detect changed snapshot artifacts when pom has not changed"() {
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url "http://localhost:${server.port}/repo" }
-}
-
-configurations { compile }
-configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-
-dependencies { 
-    compile "org.gradle:unique:1.0-SNAPSHOT" 
-    compile "org.gradle:nonunique:1.0-SNAPSHOT" 
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when: "snapshot modules are published"
-        def uniqueVersionModule = mavenRepo().module("org.gradle", "unique", "1.0-SNAPSHOT").publish()
-        def nonUniqueVersionModule = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
-
-        and: "Server handles requests"
-        expectModuleServed(uniqueVersionModule, '/repo', false, true)
-        expectModuleServed(nonUniqueVersionModule, '/repo', false, true)
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then: "Snapshots are downloaded"
-        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
-        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
-        def nonUniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
-
-        when: "Change the snapshot artifacts directly: do not change the pom"
-        uniqueVersionModule.artifactFile << 'more content'
-        nonUniqueVersionModule.artifactFile << 'more content'
-
-        and: "No server requests"
-        expectModuleServed(uniqueVersionModule, '/repo', true, true)
-        expectModuleServed(nonUniqueVersionModule, '/repo', true, true)
-
-        and: "Resolve dependencies again"
-        run 'retrieve'
-
-        then:
-        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
-        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot)
-    }
-
-    def "uses cached snapshots from a Maven HTTP repository until the snapshot timeout is reached"() {
-        server.start()
-
-        given:
-        buildFile << """
-repositories {
-    maven { url "http://localhost:${server.port}/repo" }
-}
-
-configurations { compile }
-
-if (project.hasProperty('noTimeout')) {
-    configurations.all {
-        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-    }
-}
-
-dependencies {
-    compile "org.gradle:unique:1.0-SNAPSHOT"
-    compile "org.gradle:nonunique:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'libs'
-    from configurations.compile
-}
-"""
-
-        when: "snapshot modules are published"
-        def uniqueVersionModule = mavenRepo().module("org.gradle", "unique", "1.0-SNAPSHOT")
-        uniqueVersionModule.publish()
-        def nonUniqueVersionModule = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots()
-        nonUniqueVersionModule.publish()
-
-        and: "Server handles requests"
-        expectModuleServed(uniqueVersionModule, '/repo')
-        expectModuleServed(nonUniqueVersionModule, '/repo')
-
-        and: "We resolve dependencies"
-        run 'retrieve'
-
-        then: "Snapshots are downloaded"
-        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
-        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
-        def nonUniqueJarSnapshot = file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).snapshot()
-
-        when: "Republish the snapshots"
-        uniqueVersionModule.publishWithChangedContent()
-        nonUniqueVersionModule.publishWithChangedContent()
-
-        and: "No server requests"
-        server.resetExpectations()
-
-        and: "Resolve dependencies again, with cached versions"
-        run 'retrieve'
-
-        then:
-        file('libs/unique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(uniqueJarSnapshot)
-        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(nonUniqueJarSnapshot)
-
-        when: "Server handles requests"
-        expectModuleServed(uniqueVersionModule, '/repo', true, true)
-        expectModuleServed(nonUniqueVersionModule, '/repo', true, true)
-
-        and: "Resolve dependencies with cache expired"
-        executer.withArguments("-PnoTimeout")
-        run 'retrieve'
-
-        then:
-        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
-        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
-        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot);
-    }
-
-    def "does not download snapshot artifacts after expiry when snapshot has not changed"() {
-        server.start()
-
-        buildFile << """
-repositories {
-    maven { url "http://localhost:${server.port}/repo" }
-}
-
-configurations { compile }
-
-configurations.all {
-    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-}
-
-dependencies {
-    compile "org.gradle:testproject:1.0-SNAPSHOT"
-}
-
-task retrieve(type: Sync) {
-    into 'build'
-    from configurations.compile
-}
-"""
-
-        when: "Publish the first snapshot"
-        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT")
-        module.publish()
-
-        and: "Server handles requests"
-        expectModuleServed(module, '/repo')
-
-        and:
-        run 'retrieve'
-
-        then:
-        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
-        def snapshot = file('build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile).snapshot()
-
-        when: "Server handles requests"
-        server.resetExpectations()
-        expectChangedProbe('/repo', module, false)
-
-        // Retrieve again with zero timeout should check for updated snapshot
-        and:
-        def result = run 'retrieve'
-
-        then:
-        result.assertTaskSkipped(':retrieve')
-        file('build/testproject-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshot);
-    }
-
-    def "does not download snapshot artifacts more than once per build"() {
-        server.start()
-        given:
-        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT")
-        module.publish()
-
-        and:
-        settingsFile << "include 'a', 'b'"
-        buildFile << """
-allprojects {
-    repositories {
-        maven { url "http://localhost:${server.port}/repo" }
-    }
-
-    configurations { compile }
-
-    configurations.all {
-        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-    }
-
-    dependencies {
-        compile "org.gradle:testproject:1.0-SNAPSHOT"
-    }
-
-    task retrieve(type: Sync) {
-        into 'build'
-        from configurations.compile
-    }
-}
-"""
-        when: "Module is requested once"
-        expectModuleServed(module, '/repo')
-
-        then:
-        run 'retrieve'
-
-        and:
-        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
-        file('a/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
-        file('b/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
-    }
-
-    def "can update snapshot artifact during build even if it is locked earlier in build"() {
-        server.start()
-        given:
-        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
-        def module2 = new MavenRepository(file('repo2')).module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publishWithChangedContent()
-        module2.artifactFile << module2.artifactFile.bytes // ensure it's a different length to the first one
-
-        and:
-        settingsFile << "include 'lock', 'resolve'"
-        buildFile << """
-def fileLocks = [:]
-subprojects {
-    repositories {
-        maven { url "http://localhost:${server.port}/repo" }
-    }
-
-    configurations { compile }
-
-    configurations.all {
-        resolutionStrategy.resolutionRules.eachModule({ module ->
-            module.refresh()
-        } as Action)
-    }
-
-    dependencies {
-        compile "org.gradle:testproject:1.0-SNAPSHOT"
-    }
-
-    task lock << {
-        configurations.compile.each { file ->
-            println "locking " + file
-            def lockFile = new RandomAccessFile(file.canonicalPath, 'r')
-            fileLocks[file] = lockFile
-        }
-    }
-
-    task retrieve(type: Sync) {
-        into 'build'
-        from configurations.compile
-    }
-    retrieve.dependsOn 'lock'
-}
-project('resolve') {
-    retrieve.dependsOn ':lock:retrieve'
-
-    task cleanup << {
-        fileLocks.each { key, value ->
-            println "unlocking " + key
-            value.close()
-        }
-    }
-    cleanup.dependsOn 'retrieve'
-}
-"""
-        when: "Module is requested once"
-        def moduleName = module.artifactId
-        def prefix = "/repo"
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
-
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module2.moduleDir.file("maven-metadata.xml"))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}.sha1", module2.sha1File(module2.pomFile))
-        server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module2.pomFile)
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module2.pomFile)
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module2.moduleDir.file("maven-metadata.xml"))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module2.sha1File(module2.artifactFile))
-        server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module2.artifactFile)
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module2.artifactFile)
-
-        then:
-        run 'cleanup'
-
-        and:
-        file('lock/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile)
-        file('resolve/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module2.artifactFile)
-    }
-
-    def "avoid redownload unchanged artifact when no checksum available"() {
-        server.start()
-
-        given:
-        buildFile << """
-            repositories {
-                maven { url "http://localhost:${server.port}/repo" }
-            }
-
-            configurations { compile }
-
-            configurations.all {
-                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
-            }
-
-            dependencies {
-                compile group: "group", name: "projectA", version: "1.1-SNAPSHOT"
-            }
-
-            task retrieve(type: Copy) {
-                into 'build'
-                from configurations.compile
-            }
-        """
-
-        and:
-        def module = mavenRepo().module("group", "projectA", "1.1-SNAPSHOT").withNonUniqueSnapshots().publish()
-        // Set the last modified to something that's not going to be anything “else”.
-        // There are lots of dates floating around in a resolution and we want to make
-        // sure we use this.
-        module.artifactFile.setLastModified(2000)
-        module.pomFile.setLastModified(6000)
-
-        def base = "/repo/group/projectA/1.1-SNAPSHOT"
-        def metaDataPath = "$base/maven-metadata.xml"
-        def pomPath = "$base/$module.pomFile.name"
-        def pomSha1Path = "${pomPath}.sha1"
-        def originalPomLastMod = module.pomFile.lastModified()
-        def originalPomContentLength = module.pomFile.length()
-        def jarPath = "$base/$module.artifactFile.name"
-        def jarSha1Path = "${jarPath}.sha1"
-        def originalJarLastMod = module.artifactFile.lastModified()
-        def originalJarContentLength = module.artifactFile.length()
-
-        when:
-        server.expectGet(metaDataPath, module.mavenMetaDataFile)
-        server.expectGet(pomPath, module.pomFile)
-        server.expectGet(metaDataPath, module.mavenMetaDataFile)
-        server.expectGet(jarPath, module.artifactFile)
-
-        run "retrieve"
-
-        then:
-        def downloadedJarFile = file("build/projectA-1.1-SNAPSHOT.jar")
-        downloadedJarFile.assertIsCopyOf(module.artifactFile)
-        def initialDownloadJarFileSnapshot = downloadedJarFile.snapshot()
-
-        // Do change the jar, so we can check that the new version wasn't downloaded
-        module.publishWithChangedContent()
-
-        when:
-        server.resetExpectations()
-        server.expectGet(metaDataPath, module.mavenMetaDataFile)
-        server.expectGetMissing(pomSha1Path)
-        server.expectHead(pomPath, module.pomFile, originalPomLastMod, originalPomContentLength)
-        server.expectGet(metaDataPath, module.mavenMetaDataFile)
-        server.expectGetMissing(jarSha1Path)
-        server.expectHead(jarPath, module.artifactFile, originalJarLastMod, originalJarContentLength)
-
-        run "retrieve"
-
-        then:
-        downloadedJarFile.assertHasNotChangedSince(initialDownloadJarFileSnapshot)
-
-        when:
-        server.resetExpectations()
-        server.expectGet(metaDataPath, module.mavenMetaDataFile)
-        server.expectGetMissing(pomSha1Path)
-        server.expectHead(pomPath, module.pomFile)
-        server.expectGet(pomPath, module.pomFile)
-        server.expectGet(metaDataPath, module.mavenMetaDataFile)
-        server.expectGetMissing(jarSha1Path)
-        server.expectHead(jarPath, module.artifactFile)
-        server.expectGet(jarPath, module.artifactFile)
-
-
-        run "retrieve"
-
-        then:
-        downloadedJarFile.assertHasChangedSince(initialDownloadJarFileSnapshot)
-        downloadedJarFile.assertIsCopyOf(module.artifactFile)
-    }
-
-    private expectModuleServed(MavenModule module, def prefix, boolean sha1requests = false, boolean headRequests = false) {
-        def moduleName = module.artifactId;
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
-        // TODO - should only ask for metadata once
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
-
-        if (sha1requests) {
-            server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}.sha1", module.sha1File(module.pomFile))
-            server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module.sha1File(module.artifactFile))
-        }
-
-        if (headRequests) {
-            server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
-            server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
-        }
-    }
-
-    private expectReuseModuleArtifacts(MavenModule module, def prefix, boolean metaDataMatch) {
-        def moduleName = module.artifactId;
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
-        
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}.sha1", module.sha1File(module.pomFile))
-        // TODO - should only ask for metadata once
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
-        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module.sha1File(module.artifactFile))
-    }
-
-    private expectChangedProbe(prefix, MavenModule module, boolean expectSha1) {
-        module.expectMetaDataGet(server, prefix)
-        module.expectPomHead(server, prefix)
-        if (expectSha1) {
-            module.expectPomSha1Get(server, prefix)
-        }
-
-        module.expectMetaDataGet(server, prefix)
-        module.expectArtifactHead(server, prefix)
-        if (expectSha1) {
-            module.expectArtifactSha1Get(server, prefix)
-        }
-    }
-    
-    private expectModuleMissing(MavenModule module, def prefix) {
-        def moduleName = module.artifactId;
-        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml")
-        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${moduleName}-1.0-SNAPSHOT.pom")
-        // TODO - should only ask for metadata once
-        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml")
-        server.expectHeadMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${moduleName}-1.0-SNAPSHOT.jar")
-    }
-
-
-}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
new file mode 100644
index 0000000..79ed593
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenSnapshotResolveIntegrationTest.groovy
@@ -0,0 +1,592 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.resolve.maven
+
+import org.gradle.integtests.fixtures.MavenModule
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.resolve.AbstractDependencyResolutionTest
+
+class MavenSnapshotResolveIntegrationTest extends AbstractDependencyResolutionTest {
+
+    def "can find and cache snapshots in multiple Maven HTTP repositories"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo1" }
+    maven { url "http://localhost:${server.port}/repo2" }
+}
+
+configurations { compile }
+
+dependencies {
+    compile "org.gradle:projectA:1.0-SNAPSHOT"
+    compile "org.gradle:projectB:1.0-SNAPSHOT"
+    compile "org.gradle:nonunique:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        and: "snapshot modules are published"
+        def projectA = mavenRepo().module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
+        def projectB = mavenRepo().module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
+        def nonUnique = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+
+        when: "Server provides projectA from repo1"
+        expectModuleServed(projectA, '/repo1')
+
+        and: "Server provides projectB from repo2"
+        expectModuleMissing(projectB, '/repo1')
+        expectModuleServed(projectB, '/repo2')
+
+        and: "Server provides nonunique snapshot from repo2"
+        expectModuleMissing(nonUnique, '/repo1')
+        expectModuleServed(nonUnique, '/repo2')
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
+        def snapshotNonUnique = file('libs/nonunique-1.0-SNAPSHOT.jar').snapshot()
+
+        when: "We resolve with snapshots cached: no server requests"
+        server.resetExpectations()
+        def result = run('retrieve')
+
+        then: "Everything is up to date"
+        result.assertTaskSkipped(':retrieve')
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotNonUnique);
+    }
+
+    def "can find and cache snapshots in Maven HTTP repository with additional artifact urls"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven {
+        url "http://localhost:${server.port}/repo1"
+        artifactUrls "http://localhost:${server.port}/repo2"
+    }
+}
+
+configurations { compile }
+
+dependencies {
+    compile "org.gradle:projectA:1.0-SNAPSHOT"
+    compile "org.gradle:projectB:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        and: "snapshot modules are published"
+        def projectA = mavenRepo().module("org.gradle", "projectA", "1.0-SNAPSHOT").publish()
+        def projectB = mavenRepo().module("org.gradle", "projectB", "1.0-SNAPSHOT").publish()
+
+        when: "Server provides projectA from repo1"
+        expectModuleServed(projectA, '/repo1')
+
+        and: "Server provides projectB with artifact in repo2"
+        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/maven-metadata.xml", projectB.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/${projectB.pomFile.name}", projectB.pomFile)
+        server.expectGet("/repo1/org/gradle/projectB/1.0-SNAPSHOT/maven-metadata.xml", projectB.moduleDir.file("maven-metadata.xml"))
+        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/${projectB.artifactFile.name}")
+        server.expectGetMissing("/repo1/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar")
+
+        // TODO: This is not correct - should be looking for jar with unique version to fetch snapshot
+        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/projectB-1.0-SNAPSHOT.jar", projectB.artifactFile)
+//        server.expectGet("/repo2/org/gradle/projectB/1.0-SNAPSHOT/${projectB.artifactFile.name}",  projectB.artifactFile)
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('projectA-1.0-SNAPSHOT.jar', 'projectB-1.0-SNAPSHOT.jar')
+        def snapshotA = file('libs/projectA-1.0-SNAPSHOT.jar').snapshot()
+        def snapshotB = file('libs/projectB-1.0-SNAPSHOT.jar').snapshot()
+
+        when: "We resolve with snapshots cached: no server requests"
+        server.resetExpectations()
+        def result = run('retrieve')
+
+        then: "Everything is up to date"
+        result.assertTaskSkipped(':retrieve')
+        file('libs/projectA-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotA);
+        file('libs/projectB-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshotB);
+    }
+
+    def "will detect changed snapshot artifacts when pom has not changed"() {
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+configurations.compile.resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+
+dependencies { 
+    compile "org.gradle:unique:1.0-SNAPSHOT" 
+    compile "org.gradle:nonunique:1.0-SNAPSHOT" 
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when: "snapshot modules are published"
+        def uniqueVersionModule = mavenRepo().module("org.gradle", "unique", "1.0-SNAPSHOT").publish()
+        def nonUniqueVersionModule = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+
+        and: "Server handles requests"
+        expectModuleServed(uniqueVersionModule, '/repo', false, false)
+        expectModuleServed(nonUniqueVersionModule, '/repo', false, false)
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
+        def nonUniqueJarSnapshot = file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).snapshot()
+        server.resetExpectations()
+
+        when: "Change the snapshot artifacts directly: do not change the pom"
+        uniqueVersionModule.artifactFile << 'more content'
+        nonUniqueVersionModule.artifactFile << 'more content'
+
+        and: "No server requests"
+        expectChangedArtifactServed(uniqueVersionModule, '/repo')
+        expectChangedArtifactServed(nonUniqueVersionModule, '/repo')
+
+        and: "Resolve dependencies again"
+        run 'retrieve'
+
+        then:
+        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot)
+    }
+
+    def "uses cached snapshots from a Maven HTTP repository until the snapshot timeout is reached"() {
+        server.start()
+
+        given:
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+if (project.hasProperty('noTimeout')) {
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+}
+
+dependencies {
+    compile "org.gradle:unique:1.0-SNAPSHOT"
+    compile "org.gradle:nonunique:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'libs'
+    from configurations.compile
+}
+"""
+
+        when: "snapshot modules are published"
+        def uniqueVersionModule = mavenRepo().module("org.gradle", "unique", "1.0-SNAPSHOT")
+        uniqueVersionModule.publish()
+        def nonUniqueVersionModule = mavenRepo().module("org.gradle", "nonunique", "1.0-SNAPSHOT").withNonUniqueSnapshots()
+        nonUniqueVersionModule.publish()
+
+        and: "Server handles requests"
+        expectModuleServed(uniqueVersionModule, '/repo')
+        expectModuleServed(nonUniqueVersionModule, '/repo')
+
+        and: "We resolve dependencies"
+        run 'retrieve'
+
+        then: "Snapshots are downloaded"
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        def uniqueJarSnapshot = file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).snapshot()
+        def nonUniqueJarSnapshot = file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).snapshot()
+
+        when: "Republish the snapshots"
+        uniqueVersionModule.publishWithChangedContent()
+        nonUniqueVersionModule.publishWithChangedContent()
+
+        and: "No server requests"
+        server.resetExpectations()
+
+        and: "Resolve dependencies again, with cached versions"
+        run 'retrieve'
+
+        then:
+        file('libs/unique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertHasNotChangedSince(nonUniqueJarSnapshot)
+
+        when: "Server handles requests"
+        expectModuleServed(uniqueVersionModule, '/repo', true, true)
+        expectModuleServed(nonUniqueVersionModule, '/repo', true, true)
+
+        and: "Resolve dependencies with cache expired"
+        executer.withArguments("-PnoTimeout")
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('unique-1.0-SNAPSHOT.jar', 'nonunique-1.0-SNAPSHOT.jar')
+        file('libs/unique-1.0-SNAPSHOT.jar').assertIsCopyOf(uniqueVersionModule.artifactFile).assertHasChangedSince(uniqueJarSnapshot)
+        file('libs/nonunique-1.0-SNAPSHOT.jar').assertIsCopyOf(nonUniqueVersionModule.artifactFile).assertHasChangedSince(nonUniqueJarSnapshot);
+    }
+
+    def "does not download snapshot artifacts after expiry when snapshot has not changed"() {
+        server.start()
+
+        buildFile << """
+repositories {
+    maven { url "http://localhost:${server.port}/repo" }
+}
+
+configurations { compile }
+
+configurations.all {
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+dependencies {
+    compile "org.gradle:testproject:1.0-SNAPSHOT"
+}
+
+task retrieve(type: Sync) {
+    into 'build'
+    from configurations.compile
+}
+"""
+
+        when: "Publish the first snapshot"
+        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT")
+        module.publish()
+
+        and: "Server handles requests"
+        expectModuleServed(module, '/repo')
+
+        and:
+        run 'retrieve'
+
+        then:
+        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        def snapshot = file('build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile).snapshot()
+
+        when: "Server handles requests"
+        server.resetExpectations()
+        expectChangedProbe('/repo', module, false)
+
+        // Retrieve again with zero timeout should check for updated snapshot
+        and:
+        def result = run 'retrieve'
+
+        then:
+        result.assertTaskSkipped(':retrieve')
+        file('build/testproject-1.0-SNAPSHOT.jar').assertHasNotChangedSince(snapshot);
+    }
+
+    def "does not download snapshot artifacts more than once per build"() {
+        server.start()
+        given:
+        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT")
+        module.publish()
+
+        and:
+        settingsFile << "include 'a', 'b'"
+        buildFile << """
+allprojects {
+    repositories {
+        maven { url "http://localhost:${server.port}/repo" }
+    }
+
+    configurations { compile }
+
+    configurations.all {
+        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+    }
+
+    dependencies {
+        compile "org.gradle:testproject:1.0-SNAPSHOT"
+    }
+
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.compile
+    }
+}
+"""
+        when: "Module is requested once"
+        expectModuleServed(module, '/repo')
+
+        then:
+        run 'retrieve'
+
+        and:
+        file('build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        file('a/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+        file('b/build').assertHasDescendants('testproject-1.0-SNAPSHOT.jar')
+    }
+
+    def "can update snapshot artifact during build even if it is locked earlier in build"() {
+        server.start()
+        given:
+        def module = mavenRepo().module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+        def module2 = new MavenRepository(file('repo2')).module("org.gradle", "testproject", "1.0-SNAPSHOT").withNonUniqueSnapshots().publish()
+        module2.artifactFile << module2.artifactFile.bytes // ensure it's a different length to the first one
+        module2.pomFile << '    ' // ensure it's a different length to the first one
+
+        and:
+        settingsFile << "include 'lock', 'resolve'"
+        buildFile << """
+def fileLocks = [:]
+subprojects {
+    repositories {
+        maven { url "http://localhost:${server.port}/repo" }
+    }
+
+    configurations { compile }
+
+    configurations.all {
+        resolutionStrategy.resolutionRules.eachModule({ module ->
+            module.refresh()
+        } as Action)
+    }
+
+    dependencies {
+        compile "org.gradle:testproject:1.0-SNAPSHOT"
+    }
+
+    task lock << {
+        configurations.compile.each { file ->
+            println "locking " + file
+            def lockFile = new RandomAccessFile(file.canonicalPath, 'r')
+            fileLocks[file] = lockFile
+        }
+    }
+
+    task retrieve(type: Sync) {
+        into 'build'
+        from configurations.compile
+    }
+    retrieve.dependsOn 'lock'
+}
+project('resolve') {
+    retrieve.dependsOn ':lock:retrieve'
+
+    task cleanup << {
+        fileLocks.each { key, value ->
+            println "unlocking " + key
+            value.close()
+        }
+    }
+    cleanup.dependsOn 'retrieve'
+}
+"""
+        when: "Module is requested once"
+        def moduleName = module.artifactId
+        def prefix = "/repo"
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module2.moduleDir.file("maven-metadata.xml"))
+        server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module2.pomFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}.sha1", module2.sha1File(module2.pomFile))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module2.pomFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module2.moduleDir.file("maven-metadata.xml"))
+        server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module2.artifactFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module2.sha1File(module2.artifactFile))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module2.artifactFile)
+
+        then:
+        run 'cleanup'
+
+        and:
+        file('lock/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module.artifactFile)
+        file('resolve/build/testproject-1.0-SNAPSHOT.jar').assertIsCopyOf(module2.artifactFile)
+    }
+
+    def "avoid redownload unchanged artifact when no checksum available"() {
+        server.start()
+
+        given:
+        buildFile << """
+            repositories {
+                maven { url "http://localhost:${server.port}/repo" }
+            }
+
+            configurations { compile }
+
+            configurations.all {
+                resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+            }
+
+            dependencies {
+                compile group: "group", name: "projectA", version: "1.1-SNAPSHOT"
+            }
+
+            task retrieve(type: Copy) {
+                into 'build'
+                from configurations.compile
+            }
+        """
+
+        and:
+        def module = mavenRepo().module("group", "projectA", "1.1-SNAPSHOT").withNonUniqueSnapshots().publish()
+        // Set the last modified to something that's not going to be anything “else”.
+        // There are lots of dates floating around in a resolution and we want to make
+        // sure we use this.
+        module.artifactFile.setLastModified(2000)
+        module.pomFile.setLastModified(6000)
+
+        def base = "/repo/group/projectA/1.1-SNAPSHOT"
+        def metaDataPath = "$base/maven-metadata.xml"
+        def pomPath = "$base/$module.pomFile.name"
+        def pomSha1Path = "${pomPath}.sha1"
+        def originalPomLastMod = module.pomFile.lastModified()
+        def originalPomContentLength = module.pomFile.length()
+        def jarPath = "$base/$module.artifactFile.name"
+        def jarSha1Path = "${jarPath}.sha1"
+        def originalJarLastMod = module.artifactFile.lastModified()
+        def originalJarContentLength = module.artifactFile.length()
+
+        when:
+        server.expectGet(metaDataPath, module.metaDataFile)
+        server.expectGet(pomPath, module.pomFile)
+        server.expectGet(metaDataPath, module.metaDataFile)
+        server.expectGet(jarPath, module.artifactFile)
+
+        run "retrieve"
+
+        then:
+        def downloadedJarFile = file("build/projectA-1.1-SNAPSHOT.jar")
+        downloadedJarFile.assertIsCopyOf(module.artifactFile)
+        def initialDownloadJarFileSnapshot = downloadedJarFile.snapshot()
+
+        // Do change the jar, so we can check that the new version wasn't downloaded
+        module.publishWithChangedContent()
+
+        when:
+        server.resetExpectations()
+        server.expectGet(metaDataPath, module.metaDataFile)
+        server.expectHead(pomPath, module.pomFile, originalPomLastMod, originalPomContentLength)
+        server.expectGet(metaDataPath, module.metaDataFile)
+        server.expectHead(jarPath, module.artifactFile, originalJarLastMod, originalJarContentLength)
+
+        run "retrieve"
+
+        then:
+        downloadedJarFile.assertHasNotChangedSince(initialDownloadJarFileSnapshot)
+
+        when:
+        server.resetExpectations()
+        server.expectGet(metaDataPath, module.metaDataFile)
+        server.expectGetMissing(pomSha1Path)
+        server.expectHead(pomPath, module.pomFile)
+        server.expectGet(pomPath, module.pomFile)
+        server.expectGet(metaDataPath, module.metaDataFile)
+        server.expectGetMissing(jarSha1Path)
+        server.expectHead(jarPath, module.artifactFile)
+        server.expectGet(jarPath, module.artifactFile)
+
+
+        run "retrieve"
+
+        then:
+        downloadedJarFile.assertHasChangedSince(initialDownloadJarFileSnapshot)
+        downloadedJarFile.assertIsCopyOf(module.artifactFile)
+    }
+
+    private expectModuleServed(MavenModule module, def prefix, boolean sha1requests = false, boolean headRequests = false) {
+        def moduleName = module.artifactId;
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
+        // TODO - should only ask for metadata once
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+
+        if (sha1requests) {
+            server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}.sha1", module.sha1File(module.pomFile))
+            server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module.sha1File(module.artifactFile))
+        }
+
+        if (headRequests) {
+            server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
+            server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+        }
+    }
+
+    private expectChangedArtifactServed(MavenModule module, def prefix, boolean sha1requests = false, boolean headRequests = false) {
+        def moduleName = module.artifactId;
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+        server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.pomFile.name}", module.pomFile)
+
+        // TODO - should only ask for metadata once
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml", module.moduleDir.file("maven-metadata.xml"))
+
+        server.expectHead("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}.sha1", module.sha1File(module.artifactFile))
+        server.expectGet("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${module.artifactFile.name}", module.artifactFile)
+    }
+
+    private expectChangedProbe(prefix, MavenModule module, boolean expectSha1) {
+        module.expectMetaDataGet(server, prefix)
+        module.expectPomHead(server, prefix)
+        if (expectSha1) {
+            module.expectPomSha1Get(server, prefix)
+        }
+
+        module.expectMetaDataGet(server, prefix)
+        module.expectArtifactHead(server, prefix)
+        if (expectSha1) {
+            module.expectArtifactSha1Get(server, prefix)
+        }
+    }
+    
+    private expectModuleMissing(MavenModule module, def prefix) {
+        def moduleName = module.artifactId;
+        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml")
+        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${moduleName}-1.0-SNAPSHOT.pom")
+        // TODO - should only ask for metadata once
+        server.expectGetMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/maven-metadata.xml")
+        server.expectHeadMissing("${prefix}/org/gradle/${moduleName}/1.0-SNAPSHOT/${moduleName}-1.0-SNAPSHOT.jar")
+    }
+
+
+}
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy
new file mode 100644
index 0000000..1f7762b
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.samples
+
+import org.junit.Rule
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import spock.lang.Specification
+
+class SamplesAnnounceIntegrationTest extends Specification {
+    @Rule GradleDistribution dist
+    @Rule GradleDistributionExecuter executer
+    @Rule Sample sample = new Sample("announce")
+
+    def "make some announcements"() {
+        // tweak sample to print all messages to standard out
+        def initScript = sample.dir.createFile("init2.gradle")
+        initScript << """
+import org.gradle.api.plugins.announce.Announcer
+import org.gradle.api.plugins.announce.internal.AnnouncerFactory
+
+gradle.projectsEvaluated {
+    rootProject.announce.announcerFactory = new AnnouncerFactory() {
+        Announcer createAnnouncer(String type) {
+            new Announcer() {
+                void send(String title, String message) {
+                    println message
+                }
+            }
+        }
+    }
+}
+        """
+
+        when:
+        def result = executer.inDirectory(sample.dir).withArguments("-I", initScript.path).withTasks("helloWorld").run()
+
+        then:
+        result.output.count("helloWorld completed!") == 2
+    }
+}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest/shared/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest/shared/build.gradle
new file mode 100644
index 0000000..3e34717
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest/shared/build.gradle
@@ -0,0 +1,20 @@
+def levelProp = property("level")
+def level = LogLevel."$levelProp"
+
+logging.level = level
+
+assert logger.debugEnabled == level <= LogLevel.DEBUG
+assert logger.infoEnabled == level <= LogLevel.INFO
+assert logger.lifecycleEnabled == level <= LogLevel.LIFECYCLE
+assert logger.warnEnabled == level <= LogLevel.WARN
+assert logger.quietEnabled == level <= LogLevel.QUIET
+assert logger.errorEnabled == level <= LogLevel.ERROR
+
+task checkLevel {
+    logging.level = level
+    doLast {
+        LogLevel.values().each { l ->
+            assert logger.isEnabled(l) == l >= level
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/deprecated/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/deprecated/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/deprecated/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/deprecated/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/buildSrc/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/buildSrc/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/buildSrc/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/buildSrc/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/external.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/external.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/external.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/external.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/init.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/init.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/init.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/init.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/nestedBuild/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/nestedBuild/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/buildSrc/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/nestedBuild/buildSrc/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/buildSrc/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/nestedBuild/buildSrc/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/nestedBuild/settings.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/nestedBuild/settings.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/nestedBuild/settings.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/project1/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/project1/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/project1/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/project1/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/project2/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/project2/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/project2/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/project2/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/settings.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/logging/settings.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/logging/settings.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/multiThreaded/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/multiThreaded/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/LoggingIntegrationTest/multiThreaded/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/logging/LoggingIntegrationTest/multiThreaded/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle
deleted file mode 100644
index 8c0b7a7..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/build.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'maven'
-group = 'group'
-version = '1.0'
-repositories { mavenCentral() }
-configurations { custom }
-dependencies {
-    custom 'commons-collections:commons-collections:3.2'
-    runtime 'commons-collections:commons-collections:3.2'
-}
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: uri("mavenRepo"))
-        }
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle
deleted file mode 100644
index 683b273..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithDependencyInMappedAndUnMappedConfiguration/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-rootProject.name = 'root'
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/build.gradle
deleted file mode 100644
index a9cad0b..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-apply plugin: 'java'
-apply plugin: 'maven'
-group = 'group'
-version = 1.0
-
-task signature {
-    ext.destFile = file("$buildDir/signature.sig")
-    doLast {
-        destFile.text = 'signature'
-    }
-}
-
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-
-artifacts {
-    archives new DefaultPublishArtifact(jar.baseName, "jar.sig", "jar.sig", null, new Date(), signature.destFile, signature)
-}
-
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: uri("mavenRepo"))
-            beforeDeployment { MavenDeployment deployment ->
-                assert deployment.pomArtifact.file.isFile()
-                assert deployment.pomArtifact.name == 'root'
-                assert deployment.mainArtifact.file == jar.archivePath
-                assert deployment.mainArtifact.name == 'root'
-                assert deployment.artifacts.size() == 3
-                assert deployment.artifacts.contains(deployment.pomArtifact)
-                assert deployment.artifacts.contains(deployment.mainArtifact)
-
-                def pomSignature = file("${buildDir}/pom.sig")
-                pomSignature.text = 'signature'
-                deployment.addArtifact new DefaultPublishArtifact(deployment.pomArtifact.name, "pom.sig", "pom.sig", null, new Date(), pomSignature)
-            }
-        }
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/settings.gradle
deleted file mode 100644
index 683b273..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithMetadataArtifacts/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-rootProject.name = 'root'
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/build.gradle
deleted file mode 100644
index 4ec1f1d..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-apply plugin: 'base'
-apply plugin: 'maven'
-
-group = 'group'
-version = 1.0
-
-task sourceJar(type: Jar) {
-    classifier = 'source'
-}
-artifacts {
-    archives sourceJar
-}
-uploadArchives {
-    repositories {
-        mavenDeployer {
-            repository(url: uri('mavenRepo'))
-        }
-    }
-}
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/settings.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/settings.gradle
deleted file mode 100644
index 683b273..0000000
--- a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/publish/maven/MavenPublicationIntegrationTest/canPublishAProjectWithNoMainArtifact/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-rootProject.name = 'root'
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/canResolveDependenciesFromMultipleMavenRepositories/build.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/producer.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/producer.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/producer.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/producer.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/projectWithMavenSnapshots.gradle b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/projectWithMavenSnapshots.gradle
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/projectWithMavenSnapshots.gradle
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/projectWithMavenSnapshots.gradle
diff --git a/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/src/main/java/org/gradle/Test.java b/subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/src/main/java/org/gradle/Test.java
similarity index 100%
rename from subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenRemoteDependencyResolutionIntegrationTest/shared/src/main/java/org/gradle/Test.java
rename to subprojects/integ-test/src/integTest/resources/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest/shared/src/main/java/org/gradle/Test.java
diff --git a/subprojects/internal-integ-testing/internal-integ-testing.gradle b/subprojects/internal-integ-testing/internal-integ-testing.gradle
index 8307cfa..ea5a522 100644
--- a/subprojects/internal-integ-testing/internal-integ-testing.gradle
+++ b/subprojects/internal-integ-testing/internal-integ-testing.gradle
@@ -31,20 +31,43 @@ dependencies {
 
 useTestFixtures(sourceSet: 'main')
 
-task prepareVersionsInfo {
-    ext.destDir = file("$buildDir/generated-resources/main")
+task prepareVersionsInfo(type: PrepareVersionsInfo) {
+   url = "http://services.gradle.org/versions/all"
+   destDir = file("$buildDir/generated-resources/main")
+   destFileName = "all-released-versions.json"
+   offline = gradle.startParameter.offline
+}
 
-    doLast {
-        def url = "http://services.gradle.org/versions/all"
-        logger.info "Getting the released versions from: $url"
+sourceSets.main.output.dir prepareVersionsInfo.destDir, builtBy: prepareVersionsInfo
 
-        def json = new URL("http://services.gradle.org/versions/all").text
-        def destFile = new File(ext.destDir, "all-released-versions.json")
-        assert destDir.mkdirs() || destDir.exists()
-        destFile.text = json
+class PrepareVersionsInfo extends DefaultTask {
+   File destDir
+   String destFileName
+   String url
+   boolean offline
 
-        logger.info "Saved released versions information in: $destFile"
-    }
-}
+   @TaskAction void prepareVersions() {
+       if (offline) {
+           logger.warn("Versions information will not be downloaded because --offline switch is used.\n"
+                   + "Without the version information certain integration tests may fail or use outdated version details.")
+           return
+       }
+       logger.info "Downloading the released versions from: $url"
+
+       def theUrl = "http://services.gradle.org/versions/all"
+       def json
+       try {
+           json = new URL(theUrl).text
+       } catch (UnknownHostException e) {
+           throw new GradleException("Unable to acquire versions info. I've tried this url: '$theUrl'.\n"
+                   + "If you don't have the network connection please run with '--offline' or exclude this task from execution via '-x'."
+                   , e)
+       }
+
+       def destFile = new File(destDir, destFileName)
+       assert destDir.mkdirs() || destDir.exists() : "Problems creating output directory for $name. Attempted to create $destDir"
+       destFile.text = json
 
-sourceSets.main.output.dir prepareVersionsInfo.destDir, builtBy: prepareVersionsInfo
\ No newline at end of file
+       logger.info "Saved released versions information in: $destFile"
+   }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy
index fedd4e9..b33af72 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractIntegrationSpec.groovy
@@ -134,4 +134,17 @@ class AbstractIntegrationSpec extends Specification {
         executer.withUserHomeDir(distribution.getUserHomeDir())
         return new GradleBackedArtifactBuilder(executer, getTestDir().file("artifacts"))
     }
+
+
+    def createZip(String name, Closure cl) {
+        TestFile zipRoot = file("${name}.root")
+        TestFile zip = file(name)
+        zipRoot.create(cl)
+        zipRoot.zipTo(zip)
+    }
+
+    def createDir(String name, Closure cl) {
+        TestFile root = file(name)
+        root.create(cl)
+    }
 }
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AvailableJavaHomes.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AvailableJavaHomes.java
index 215d3a5..d975d66 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AvailableJavaHomes.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AvailableJavaHomes.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.integtests.fixtures;
 
+import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.os.OperatingSystem;
 import org.gradle.util.GFileUtils;
-import org.gradle.internal.jvm.Jvm;
 
 import java.io.File;
 
@@ -36,9 +36,9 @@ abstract public class AvailableJavaHomes {
 
         // Use environment variables
         File javaHome = null;
-        if (jvm.isJava6Compatible()) {
+        if (jvm.getJavaVersion().isJava6Compatible()) {
             javaHome = firstAvailable("15", "17");
-        } else if (jvm.isJava5Compatible()) {
+        } else if (jvm.getJavaVersion().isJava5Compatible()) {
             javaHome = firstAvailable("16", "17");
         }
         if (javaHome != null) {
@@ -72,10 +72,10 @@ abstract public class AvailableJavaHomes {
             File[] files = installedJavas.listFiles();
             for (File file : files) {
                 if (file.getName().startsWith("jdk")) {
-                    if (jvm.isJava6() && !file.getName().contains("1.6")) {
+                    if (jvm.getJavaVersion().isJava6() && !file.getName().contains("1.6")) {
                         return file;
                     }
-                    if (jvm.isJava7() && !file.getName().contains("1.7")) {
+                    if (jvm.getJavaVersion().isJava7() && !file.getName().contains("1.7")) {
                         return file;
                     }
                 }
@@ -85,6 +85,71 @@ abstract public class AvailableJavaHomes {
         return null;
     }
 
+    public static File getBestJreAlternative() {
+        Jvm jvm = Jvm.current();
+
+        // Use environment variables
+        File jreHome = null;
+        if (jvm.getJavaVersion().isJava6Compatible()) {
+            jreHome = firstAvailableJRE("15", "17");
+        } else if (jvm.getJavaVersion().isJava5Compatible()) {
+            jreHome = firstAvailableJRE();
+        }
+        if (jreHome != null) {
+            return jreHome;
+        }
+
+        if (OperatingSystem.current().isMacOsX()) {
+            File registeredJvms = new File("/Library/Java/JavaVirtualMachines");
+            if (registeredJvms.isDirectory()) {
+                for (File candidate : registeredJvms.listFiles()) {
+                    jreHome = GFileUtils.canonicalise(new File(candidate, "Contents/Home/jre"));
+                    if (!jreHome.equals(jvm.getJavaHome()) && jreHome.isDirectory() && new File(jreHome, "bin/java").isFile()) {
+                        return jreHome;
+                    }
+                }
+            }
+        } else if (OperatingSystem.current().isLinux()) {
+            // Ubuntu specific
+            File installedJvms = new File("/usr/lib/jvm");
+            if (installedJvms.isDirectory()) {
+                for (File candidate : installedJvms.listFiles()) {
+                    jreHome = new File(GFileUtils.canonicalise(candidate), "jre");
+                    if (!jreHome.equals(jvm.getJavaHome()) && jreHome.isDirectory() && new File(jreHome, "bin/java").isFile()) {
+                        return jreHome;
+                    }
+                }
+            }
+        } else if (OperatingSystem.current().isWindows()) {
+            //very simple algorithm trying to find java on windows
+            File installedJavas = new File("c:/Program Files/Java");
+            File[] files = installedJavas.listFiles();
+            for (File file : files) {
+                if (file.getName().startsWith("jre")) {
+                    if (jvm.getJavaVersion().isJava6() && !file.getName().contains("1.6")) {
+                        return file;
+                    }
+                    if (jvm.getJavaVersion().isJava7() && !file.getName().contains("1.7")) {
+                        return file;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private static File firstAvailableJRE(String... labels) {
+        File javaHome = firstAvailable(labels);
+        if (javaHome != null) {
+            final File jre = new File(javaHome, "jre");
+            if (jre.isDirectory()) {
+                return jre;
+            }
+        }
+        return null;
+    }
+
+
     public static File firstAvailable(String... labels) {
         for (String label : labels) {
             File found = getJavaHome(label);
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
index e69ca9c..fa056ba 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/DaemonGradleExecuter.java
@@ -71,7 +71,7 @@ public class
         if(!allowExtraLogging) {
             return;
         }
-        List logOptions = asList("-i", "--info", "-d", "--debug", "-q", "--quite");
+        List logOptions = asList("-i", "--info", "-d", "--debug", "-q", "--quiet");
         boolean alreadyConfigured = CollectionUtils.containsAny(args, logOptions);
         if (!alreadyConfigured) {
             args.add("-i");
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java
index d438937..5299d4d 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/EmbeddedDaemonGradleExecuter.java
@@ -16,6 +16,7 @@
 package org.gradle.integtests.fixtures;
 
 import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
 import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.launcher.cli.ExecuteBuildAction;
@@ -96,7 +97,7 @@ public class EmbeddedDaemonGradleExecuter extends AbstractGradleExecuter {
     }
 
     private BuildActionParameters createBuildActionParameters() {
-        return new DefaultBuildActionParameters(daemonClientServices.get(BuildClientMetaData.class), getStartTime(), System.getProperties(), getEnvironmentVars(), getWorkingDir());
+        return new DefaultBuildActionParameters(daemonClientServices.get(BuildClientMetaData.class), getStartTime(), System.getProperties(), getEnvironmentVars(), getWorkingDir(), LogLevel.LIFECYCLE);
     }
 
     private long getStartTime() {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java
index 3b83010..94e0f4c 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ForkingGradleHandle.java
@@ -94,7 +94,7 @@ class ForkingGradleHandle extends OutputScrapingGradleHandle {
     }
 
     public ExecutionFailure waitForFailure() {
-        return (ExecutionFailure)waitForStop(true);
+        return (ExecutionFailure) waitForStop(true);
     }
 
     protected ExecutionResult waitForStop(boolean expectFailure) {
@@ -109,9 +109,8 @@ class ForkingGradleHandle extends OutputScrapingGradleHandle {
         if (didFail != expectFailure) {
             String message = String.format("Gradle execution %s in %s with: %s %nOutput:%n%s%n-----%nError:%n%s%n-----%n",
                     expectFailure ? "did not fail" : "failed", execHandle.getDirectory(), execHandle.getCommand(), output, error);
-            throw new RuntimeException(message);
+            throw new UnexpectedBuildFailure(message);
         }
-
         return expectFailure ? toExecutionFailure(output, error) : toExecutionResult(output, error);
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
index 7a93f1e..bf90ce0 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
@@ -18,7 +18,10 @@ package org.gradle.integtests.fixtures;
 
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.os.OperatingSystem;
-import org.gradle.util.*;
+import org.gradle.util.GradleVersion;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.gradle.util.TestFileContext;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
 import org.junit.runners.model.Statement;
@@ -65,7 +68,7 @@ public class GradleDistribution implements MethodRule, TestFileContext, BasicGra
 
     public boolean worksWith(Jvm jvm) {
         // Works with anything >= Java 5
-        return jvm.isJava5Compatible();
+        return jvm.getJavaVersion().isJava5Compatible();
     }
 
     public boolean worksWith(OperatingSystem os) {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
index 0f402d9..16ba09b 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
@@ -44,9 +44,11 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
     private boolean workingDirSet;
     private boolean userHomeSet;
     private boolean deprecationChecksOn = true;
+    private boolean stackTraceChecksOn = true;
     private Executer executerType;
     private File daemonBaseDir;
     private boolean allowExtraLogging = true;
+    private boolean mustFork;
 
     public enum Executer {
         embedded(false),
@@ -83,6 +85,14 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
         reset();
     }
 
+    public boolean isMustFork() {
+        return mustFork;
+    }
+
+    public void setMustFork(boolean mustFork) {
+        this.mustFork = mustFork;
+    }
+
     public Executer getType() {
         return executerType;
     }
@@ -100,6 +110,8 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
         workingDirSet = false;
         userHomeSet = false;
         deprecationChecksOn = true;
+        stackTraceChecksOn = true;
+        mustFork = false;
         DeprecationLogger.reset();
         return this;
     }
@@ -129,6 +141,11 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
         deprecationChecksOn = false;
         return this;
     }
+
+    public GradleDistributionExecuter withStackTraceChecksDisabled() {
+        stackTraceChecksOn = false;
+        return this;
+    }
     
     public GradleDistributionExecuter withForkingExecuter() {
         if (!executerType.forks) {
@@ -138,9 +155,11 @@ public class GradleDistributionExecuter extends AbstractDelegatingGradleExecuter
     }
 
     protected <T extends ExecutionResult> T checkResult(T result) {
-        // Assert that nothing unexpected was logged
-        assertOutputHasNoStackTraces(result);
-        assertErrorHasNoStackTraces(result);
+        if (stackTraceChecksOn) {
+            // Assert that nothing unexpected was logged
+            assertOutputHasNoStackTraces(result);
+            assertErrorHasNoStackTraces(result);
+        }
         if (deprecationChecksOn) {
             assertOutputHasNoDeprecationWarnings(result);
         }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/HttpServer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
index 5963f52..0b2ceb9 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
@@ -15,16 +15,18 @@
  */
 package org.gradle.integtests.fixtures
 
-import java.security.Principal
-import java.util.zip.GZIPOutputStream
-import javax.servlet.http.HttpServletRequest
-import javax.servlet.http.HttpServletResponse
 import org.gradle.util.hash.HashUtil
 import org.junit.rules.ExternalResource
 import org.mortbay.jetty.handler.AbstractHandler
 import org.mortbay.jetty.handler.HandlerCollection
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
+
+import java.security.Principal
+import java.util.zip.GZIPOutputStream
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
 import org.mortbay.jetty.*
 import org.mortbay.jetty.security.*
 
@@ -34,22 +36,35 @@ class HttpServer extends ExternalResource {
 
     private final Server server = new Server(0)
     private final HandlerCollection collection = new HandlerCollection()
-    private Throwable failure
     private TestUserRealm realm
-    
+    private SecurityHandler securityHandler
+    AuthScheme authenticationScheme = AuthScheme.BASIC
+    private Throwable failure
+    private final List<Expection> expections = []
+
+    enum AuthScheme {
+        BASIC(new BasicAuthHandler()), DIGEST(new DigestAuthHandler())
+
+        final AuthSchemeHandler handler;
+
+        AuthScheme(AuthSchemeHandler handler) {
+            this.handler = handler
+        }
+    }
+
     enum EtagStrategy {
         NONE({ null }),
         RAW_SHA1_HEX({ HashUtil.sha1(it as byte[]).asHexString() }),
         NEXUS_ENCODED_SHA1({ "{SHA1{" + HashUtil.sha1(it as byte[]).asHexString() + "}}" })
-        
+
         private final Closure generator
-        
+
         EtagStrategy(Closure generator) {
             this.generator = generator
         }
-        
+
         String generate(byte[] bytes) {
-            generator.call(bytes)                        
+            generator.call(bytes)
         }
     }
 
@@ -72,8 +87,7 @@ class HttpServer extends ExternalResource {
                 if (request.handled) {
                     return
                 }
-                failure = new AssertionError("Received unexpected ${request.method} request to ${target}.")
-                logger.error(failure.message)
+                onFailure(new AssertionError("Received unexpected ${request.method} request to ${target}."))
                 response.sendError(404, "'$target' does not exist")
             }
         })
@@ -85,14 +99,30 @@ class HttpServer extends ExternalResource {
     }
 
     void stop() {
+        resetExpectations()
         server?.stop()
     }
 
+    private void onFailure(Throwable failure) {
+        logger.error(failure.message)
+        if (this.failure == null) {
+            this.failure = failure
+        }
+    }
+
     void resetExpectations() {
-        if (failure != null) {
-            throw failure
+        try {
+            if (failure != null) {
+                throw failure
+            }
+            for (Expection e in expections) {
+                e.assertMet()
+            }
+        } finally {
+            failure = null
+            expections.clear()
+            collection.setHandlers()
         }
-        collection.setHandlers()
     }
 
     @Override
@@ -107,9 +137,27 @@ class HttpServer extends ExternalResource {
         allow(path, true, ['GET', 'HEAD'], fileHandler(path, srcFile))
     }
 
-    private AbstractHandler fileHandler(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
-        return new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+    /**
+     * Adds a given file at the given URL. The source file can be either a file or a directory.
+     */
+    void allowHead(String path, File srcFile) {
+        allow(path, true, ['HEAD'], fileHandler(path, srcFile))
+    }
+
+    /**
+     * Adds a given file at the given URL with the given credentials. The source file can be either a file or a directory.
+     */
+    void allowGet(String path, String username, String password, File srcFile) {
+        allow(path, true, ['GET', 'HEAD'], withAuthentication(path, username, password, fileHandler(path, srcFile)))
+    }
+
+    private Action fileHandler(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
+        return new Action() {
+            String getDisplayName() {
+                return "return contents of $srcFile.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 def file
                 if (request.pathInfo == path) {
                     file = srcFile
@@ -122,7 +170,7 @@ class HttpServer extends ExternalResource {
                 } else if (file.isDirectory()) {
                     sendDirectoryListing(response, file)
                 } else {
-                    response.sendError(404, "'$target' does not exist")
+                    response.sendError(404, "'$request.pathInfo' does not exist")
                 }
             }
         }
@@ -132,8 +180,12 @@ class HttpServer extends ExternalResource {
      * Adds a broken resource at the given URL.
      */
     void addBroken(String path) {
-        allow(path, true, null, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+        allow(path, true, null, new Action() {
+            String getDisplayName() {
+                return "return 500 broken"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 response.sendError(500, "broken")
             }
         })
@@ -143,22 +195,26 @@ class HttpServer extends ExternalResource {
      * Allows one GET request for the given URL, which return 404 status code
      */
     void expectGetMissing(String path) {
-        expect(path, false, ['GET'], new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                response.sendError(404, "not found")
-            }
-        })
+        expect(path, false, ['GET'], notFound())
     }
 
     /**
      * Allows one HEAD request for the given URL, which return 404 status code
      */
     void expectHeadMissing(String path) {
-        expect(path, false, ['HEAD'], new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+        expect(path, false, ['HEAD'], notFound())
+    }
+
+    private Action notFound() {
+        new Action() {
+            String getDisplayName() {
+                return "return 404 not found"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 response.sendError(404, "not found")
             }
-        })
+        }
     }
 
     /**
@@ -169,6 +225,13 @@ class HttpServer extends ExternalResource {
     }
 
     /**
+     * Allows one HEAD request for the given URL with http authentication.
+     */
+    void expectHead(String path, String username, String password, File srcFile, Long lastModified = null, Long contentLength = null) {
+        expect(path, false, ['HEAD'], withAuthentication(path, username, password, fileHandler(path, srcFile)))
+    }
+
+    /**
      * Allows one GET request for the given URL. Reads the request content from the given file.
      */
     void expectGet(String path, File srcFile, Long lastModified = null, Long contentLength = null) {
@@ -194,8 +257,12 @@ class HttpServer extends ExternalResource {
      * Allows one GET request for the given URL, with the response being GZip encoded.
      */
     void expectGetGZipped(String path, File srcFile) {
-        expect(path, false, ['GET'], new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+        expect(path, false, ['GET'], new Action() {
+            String getDisplayName() {
+                return "return gzipped $srcFile.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 def file = srcFile
                 if (file.isFile()) {
                     response.setHeader("Content-Encoding", "gzip")
@@ -225,8 +292,12 @@ class HttpServer extends ExternalResource {
     }
 
     private void expectRedirected(String method, String path, String location) {
-        expect(path, false, [method], new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+        expect(path, false, [method], new Action() {
+            String getDisplayName() {
+                return "redirect to $location"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 response.sendRedirect(location)
             }
         })
@@ -236,13 +307,33 @@ class HttpServer extends ExternalResource {
      * Allows one GET request for the given URL, returning an apache-compatible directory listing with the given File names.
      */
     void expectGetDirectoryListing(String path, File directory) {
-        expect(path, false, ['GET'], new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+        expect(path, false, ['GET'], new Action() {
+            String getDisplayName() {
+                return "return listing of directory $directory.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 sendDirectoryListing(response, directory)
             }
         })
     }
 
+    /**
+     * Allows one GET request for the given URL, returning an apache-compatible directory listing with the given File names.
+     */
+    void expectGetDirectoryListing(String path, String username, String password, File directory) {
+        expect(path, false, ['GET'], withAuthentication(path, username, password, new Action() {
+            String getDisplayName() {
+                return "return listing of directory $directory.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                sendDirectoryListing(response, directory)
+            }
+        }));
+    }
+
+
     private sendFile(HttpServletResponse response, File file, Long lastModified, Long contentLength) {
         if (sendLastModified) {
             response.setDateHeader(HttpHeaders.LAST_MODIFIED, lastModified ?: file.lastModified())
@@ -275,12 +366,12 @@ class HttpServer extends ExternalResource {
 
     private sendDirectoryListing(HttpServletResponse response, File directory) {
         def directoryListing = ""
-        for (String fileName: directory.list()) {
+        for (String fileName : directory.list()) {
             directoryListing += "<a href=\"$fileName\">$fileName</a>"
         }
 
         response.setContentLength(directoryListing.length())
-        response.setContentType("text/plain")
+        response.setContentType("text/html")
         response.outputStream.bytes = directoryListing.bytes
     }
 
@@ -288,8 +379,12 @@ class HttpServer extends ExternalResource {
      * Allows one PUT request for the given URL. Writes the request content to the given file.
      */
     void expectPut(String path, File destFile, int statusCode = HttpStatus.ORDINAL_200_OK) {
-        expect(path, false, ['PUT'], new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+        expect(path, false, ['PUT'], new Action() {
+            String getDisplayName() {
+                return "write request to $destFile.name and return status $statusCode"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 destFile.bytes = request.inputStream.bytes
                 response.setStatus(statusCode)
             }
@@ -300,8 +395,12 @@ class HttpServer extends ExternalResource {
      * Allows one PUT request for the given URL, with the given credentials. Writes the request content to the given file.
      */
     void expectPut(String path, String username, String password, File destFile) {
-        expect(path, false, ['PUT'], withAuthentication(path, username, password, new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+        expect(path, false, ['PUT'], withAuthentication(path, username, password, new Action() {
+            String getDisplayName() {
+                return "write request to $destFile.name"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 if (request.remoteUser != username) {
                     response.sendError(500, "unexpected username '${request.remoteUser}'")
                     return
@@ -311,57 +410,68 @@ class HttpServer extends ExternalResource {
         }))
     }
 
-    private Handler withAuthentication(String path, String username, String password, Handler handler) {
+    /**
+     * Allows PUT requests with the given credentials.
+     */
+    void allowPut(String path, String username, String password) {
+        allow(path, false, ['PUT'], withAuthentication(path, username, password, new Action() {
+            String getDisplayName() {
+                return "return 500"
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
+                response.sendError(500, "unexpected username '${request.remoteUser}'")
+            }
+        }))
+    }
+
+    private Action withAuthentication(String path, String username, String password, Action action) {
         if (realm != null) {
             assert realm.username == username
             assert realm.password == password
+            authenticationScheme.handler.addConstraint(securityHandler, path)
         } else {
             realm = new TestUserRealm()
             realm.username = username
             realm.password = password
-            def constraint = new Constraint()
-            constraint.name = Constraint.__BASIC_AUTH
-            constraint.authenticate = true
-            constraint.roles = ['*'] as String[]
-            def constraintMapping = new ConstraintMapping()
-            constraintMapping.pathSpec = path
-            constraintMapping.constraint = constraint
-            def securityHandler = new SecurityHandler()
-            securityHandler.userRealm = realm
-            securityHandler.constraintMappings = [constraintMapping] as ConstraintMapping[]
-            securityHandler.authenticator = new BasicAuthenticator()
+            securityHandler = authenticationScheme.handler.createSecurityHandler(path, realm)
             collection.addHandler(securityHandler)
         }
 
-        return new AbstractHandler() {
-            void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
+        return new Action() {
+            String getDisplayName() {
+                return action.displayName
+            }
+
+            void handle(HttpServletRequest request, HttpServletResponse response) {
                 if (request.remoteUser != username) {
                     response.sendError(500, "unexpected username '${request.remoteUser}'")
                     return
                 }
-                handler.handle(target, request, response, dispatch)
+                action.handle(request, response)
             }
         }
     }
 
-    private void expect(String path, boolean recursive, Collection<String> methods, Handler handler) {
-        boolean run
+    private void expect(String path, boolean recursive, Collection<String> methods, Action action) {
+        ExpectOne expectation = new ExpectOne(action, methods, path)
+        expections << expectation
         add(path, recursive, methods, new AbstractHandler() {
             void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                if (run) {
+                if (expectation.run) {
                     return
                 }
-                run = true
-                handler.handle(target, request, response, dispatch)
+                expectation.run = true
+                action.handle(request, response)
                 request.handled = true
             }
         })
     }
 
-    private void allow(String path, boolean recursive, Collection<String> methods, Handler handler) {
+    private void allow(String path, boolean recursive, Collection<String> methods, Action action) {
         add(path, recursive, methods, new AbstractHandler() {
             void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) {
-                handler.handle(target, request, response, dispatch)
+                action.handle(request, response)
                 request.handled = true
             }
         })
@@ -385,7 +495,98 @@ class HttpServer extends ExternalResource {
     }
 
     int getPort() {
-        return server.connectors[0].localPort
+        def port = server.connectors[0].localPort
+        if (port < 0) {
+            throw new RuntimeException("""No port available for HTTP server. Still starting perhaps?
+connector: ${server.connectors[0]}
+connector state: ${server.connectors[0].dump()}
+server state: ${server.dump()}
+""")
+        }
+        return port
+    }
+
+    interface Expection {
+        void assertMet()
+    }
+
+    static class ExpectOne implements Expection {
+        boolean run
+        final Action action
+        final Collection<String> methods
+        final String path
+
+        ExpectOne(Action action, Collection<String> methods, String path) {
+            this.action = action
+            this.methods = methods
+            this.path = path
+        }
+
+        void assertMet() {
+            if (!run) {
+                throw new AssertionError("Expected HTTP request not received: ${methods.size() == 1 ? methods[0] : methods} $path and $action.displayName")
+            }
+        }
+    }
+
+    interface Action {
+        String getDisplayName()
+
+        void handle(HttpServletRequest request, HttpServletResponse response)
+    }
+
+    abstract static class AuthSchemeHandler {
+        public SecurityHandler createSecurityHandler(String path, TestUserRealm realm) {
+            def constraintMapping = createConstraintMapping(path)
+            def securityHandler = new SecurityHandler()
+            securityHandler.userRealm = realm
+            securityHandler.constraintMappings = [constraintMapping] as ConstraintMapping[]
+            securityHandler.authenticator = authenticator
+            return securityHandler
+        }
+
+        public void addConstraint(SecurityHandler securityHandler, String path) {
+            securityHandler.constraintMappings = (securityHandler.constraintMappings as List) + createConstraintMapping(path)
+        }
+
+        private ConstraintMapping createConstraintMapping(String path) {
+            def constraint = new Constraint()
+            constraint.name = constraintName()
+            constraint.authenticate = true
+            constraint.roles = ['*'] as String[]
+            def constraintMapping = new ConstraintMapping()
+            constraintMapping.pathSpec = path
+            constraintMapping.constraint = constraint
+            return constraintMapping
+        }
+
+        protected abstract String constraintName();
+
+        protected abstract Authenticator getAuthenticator();
+    }
+
+    public static class BasicAuthHandler extends AuthSchemeHandler {
+        @Override
+        protected String constraintName() {
+            return Constraint.__BASIC_AUTH
+        }
+
+        @Override
+        protected Authenticator getAuthenticator() {
+            return new BasicAuthenticator()
+        }
+    }
+
+    public static class DigestAuthHandler extends AuthSchemeHandler {
+        @Override
+        protected String constraintName() {
+            return Constraint.__DIGEST_AUTH
+        }
+
+        @Override
+        protected Authenticator getAuthenticator() {
+            return new DigestAuthenticator()
+        }
     }
 
     static class TestUserRealm implements UserRealm {
@@ -393,7 +594,8 @@ class HttpServer extends ExternalResource {
         String password
 
         Principal authenticate(String username, Object credentials, Request request) {
-            if (username == this.username && password == credentials) {
+            Password passwordCred = new Password(password)
+            if (username == this.username && passwordCred.check(credentials)) {
                 return getPrincipal(username)
             }
             return null
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
index 1623c14..55e405e 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
@@ -16,7 +16,6 @@
 
 package org.gradle.integtests.fixtures;
 
-import junit.framework.AssertionFailedError;
 import org.gradle.BuildListener;
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
@@ -66,7 +65,11 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
         OutputListenerImpl errorListener = new OutputListenerImpl();
         BuildListenerImpl buildListener = new BuildListenerImpl();
         BuildResult result = doRun(outputListener, errorListener, buildListener);
-        result.rethrowFailure();
+        try {
+            result.rethrowFailure();
+        } catch (Exception e) {
+            throw new UnexpectedBuildFailure(e);
+        }
         return new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
                 outputListener.toString(), errorListener.toString());
     }
@@ -78,7 +81,7 @@ public class InProcessGradleExecuter extends AbstractGradleExecuter {
         BuildListenerImpl buildListener = new BuildListenerImpl();
         try {
             doRun(outputListener, errorListener, buildListener).rethrowFailure();
-            throw new AssertionFailedError("expected build to fail but it did not.");
+            throw new AssertionError("expected build to fail but it did not.");
         } catch (GradleException e) {
             return new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
                     outputListener.writer.toString(), errorListener.writer.toString(), e);
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy
index d526699..863b277 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/IvyRepository.groovy
@@ -16,7 +16,6 @@
 package org.gradle.integtests.fixtures
 
 import java.util.regex.Pattern
-import junit.framework.AssertionFailedError
 
 import org.gradle.util.TestFile
 import org.gradle.util.hash.HashUtil
@@ -84,7 +83,7 @@ class IvyModule {
         return this
     }
 
-    File getIvyFile() {
+    TestFile getIvyFile() {
         return moduleDir.file("ivy-${revision}.xml")
     }
 
@@ -92,6 +91,10 @@ class IvyModule {
         return moduleDir.file("$module-${revision}.jar")
     }
 
+    TestFile sha1File(File file) {
+        return moduleDir.file("${file.name}.sha1")
+    }
+
     /**
      * Publishes ivy.xml plus all artifacts with different content to previous publication.
      */
@@ -106,121 +109,139 @@ class IvyModule {
     IvyModule publish() {
         moduleDir.createDir()
 
-        ivyFile.text = """<?xml version="1.0" encoding="UTF-8"?>
+        publish(ivyFile) {
+            ivyFile.text = """<?xml version="1.0" encoding="UTF-8"?>
 <ivy-module version="1.0" xmlns:m="http://ant.apache.org/ivy/maven">
+    <!-- ${publishCount} -->
 	<info organisation="${organisation}"
 		module="${module}"
 		revision="${revision}"
 		status="${status}"
 	/>
 	<configurations>"""
-        configurations.each { name, config ->
-            ivyFile << "<conf name='$name' visibility='public'"
-            if (config.extendsFrom) {
-                ivyFile << " extends='${config.extendsFrom.join(',')}'"
+            configurations.each { name, config ->
+                ivyFile << "<conf name='$name' visibility='public'"
+                if (config.extendsFrom) {
+                    ivyFile << " extends='${config.extendsFrom.join(',')}'"
+                }
+                if (!config.transitive) {
+                    ivyFile << " transitive='false'"
+                }
+                ivyFile << "/>"
             }
-            if (!config.transitive) {
-                ivyFile << " transitive='false'"
-            }
-            ivyFile << "/>"
-        }
-	ivyFile << """</configurations>
+            ivyFile << """</configurations>
 	<publications>
 """
-        artifacts.each { artifact ->
-            file(artifact) << "add some content so that file size isn't zero: $publishCount"
-            ivyFile << """<artifact name="${artifact.name}" type="${artifact.type}" ext="${artifact.type}" conf="*" m:classifier="${artifact.classifier ?: ''}"/>
+            artifacts.each { artifact ->
+                def artifactFile = file(artifact)
+                publish(artifactFile) {
+                    artifactFile << "${artifactFile.name} : $publishCount"
+                }
+                ivyFile << """<artifact name="${artifact.name}" type="${artifact.type}" ext="${artifact.type}" conf="*" m:classifier="${artifact.classifier ?: ''}"/>
 """
-        }
-        ivyFile << """
+            }
+            ivyFile << """
 	</publications>
 	<dependencies>
 """
-        dependencies.each { dep ->
-            ivyFile << """<dependency org="${dep.organisation}" name="${dep.module}" rev="${dep.revision}"/>
+            dependencies.each { dep ->
+                ivyFile << """<dependency org="${dep.organisation}" name="${dep.module}" rev="${dep.revision}"/>
 """
-        }
-        ivyFile << """
+            }
+            ivyFile << """
     </dependencies>
 </ivy-module>
         """
-
+        }
         return this
     }
 
-    private File file(def artifact) {
+    private TestFile file(artifact) {
         return moduleDir.file("${artifact.name}-${revision}${artifact.classifier ? '-' + artifact.classifier : ''}.${artifact.type}")
     }
 
+    private publish(File file, Closure cl) {
+        def lastModifiedTime = file.exists() ? file.lastModified() : null
+        cl.call(file)
+        if (lastModifiedTime != null) {
+            file.setLastModified(lastModifiedTime + 2000)
+        }
+        sha1File(file).text = getHash(file, "SHA1")
+    }
+
     /**
      * Asserts that exactly the given artifacts have been published.
      */
     void assertArtifactsPublished(String... names) {
-        assert moduleDir.list() as Set == names as Set
+        Set allFileNames = [];
+        for (name in names) {
+            allFileNames += [name, "${name}.sha1"]
+        }
+        assert moduleDir.list() as Set == allFileNames
     }
 
-    private String getHash(File file, String algorithm) {
-        return HashUtil.createHash(file, algorithm).asHexString()
+    void assertChecksumPublishedFor(TestFile testFile) {
+        def sha1File = sha1File(testFile)
+        sha1File.assertIsFile()
+        assert sha1File.text == getHash(testFile, "SHA1")
     }
 
-    TestFile sha1File(File file) {
-        def sha1File = moduleDir.file("${file.name}.sha1")
-        sha1File.text = getHash(file, "SHA1")
-        return sha1File
+    String getHash(File file, String algorithm) {
+        return HashUtil.createHash(file, algorithm).asHexString()
     }
 
     IvyDescriptor getIvy() {
         return new IvyDescriptor(ivyFile)
     }
 
-    public expectIvyHead(HttpServer server, prefix = null) {
+    def expectIvyHead(HttpServer server, prefix = null) {
         server.expectHead(ivyPath(prefix), ivyFile)
     }
 
-    public expectIvyGet(HttpServer server, prefix = null) {
+    def expectIvyGet(HttpServer server, prefix = null) {
         server.expectGet(ivyPath(prefix), ivyFile)
     }
 
-    public ivyPath(prefix = null) {
+    def ivyPath(prefix = null) {
         path(prefix, ivyFile.name)
     }
 
-    public expectIvySha1Get(HttpServer server, prefix = null) {
+    def expectIvySha1Get(HttpServer server, prefix = null) {
         server.expectGet(ivySha1Path(prefix), sha1File(ivyFile))
     }
 
-    public ivySha1Path(prefix = null) {
+    def ivySha1Path(prefix = null) {
         ivyPath(prefix) + ".sha1"
     }
 
-    public expectArtifactHead(HttpServer server, prefix = null) {
+    def expectArtifactHead(HttpServer server, prefix = null) {
         server.expectHead(artifactPath(prefix), jarFile)
     }
 
-    public expectArtifactGet(HttpServer server, prefix = null) {
+    def expectArtifactGet(HttpServer server, prefix = null) {
         server.expectGet(artifactPath(prefix), jarFile)
     }
 
-    public artifactPath(prefix = null) {
+    def artifactPath(prefix = null) {
         path(prefix, jarFile.name)
     }
 
-    public expectArtifactSha1Get(HttpServer server, prefix = null) {
+    def expectArtifactSha1Get(HttpServer server, prefix = null) {
         server.expectGet(artifactSha1Path(prefix), sha1File(jarFile))
     }
 
-    public artifactSha1Path(prefix = null) {
+    def artifactSha1Path(prefix = null) {
         artifactPath(prefix) + ".sha1"
     }
 
-    public path(prefix = null, String filename) {
+    def path(prefix = null, String filename) {
         "${prefix == null ? "" : prefix}/${organisation}/${module}/${revision}/${filename}"
     }
-
 }
 
 class IvyDescriptor {
     final Map<String, IvyConfiguration> configurations = [:]
+    Map<String, IvyArtifact> artifacts = [:]
 
     IvyDescriptor(File ivyFile) {
         def ivy = new XmlParser().parse(ivyFile)
@@ -237,6 +258,15 @@ class IvyDescriptor {
             }
             config.addDependency(dep. at org, dep. at name, dep. at rev)
         }
+        ivy.publications.artifact.each { artifact ->
+            def ivyArtifact = new IvyArtifact(name: artifact. at name, type: artifact. at type, ext: artifact. at ext, conf: artifact. at conf.split(",") as List)
+            artifacts.put(ivyArtifact.name, ivyArtifact)
+        }
+    }
+
+    IvyArtifact expectArtifact(String name) {
+        assert artifacts.containsKey(name)
+        artifacts[name]
     }
 }
 
@@ -250,7 +280,14 @@ class IvyConfiguration {
     void assertDependsOn(String org, String module, String revision) {
         def dep = [org: org, module: module, revision: revision]
         if (!dependencies.find { it == dep}) {
-            throw new AssertionFailedError("Could not find expected dependency $dep. Actual: $dependencies")
+            throw new AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
         }
     }
 }
+
+class IvyArtifact {
+    String name
+    String type
+    String ext
+    List<String> conf
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
index f071250..1573b7f 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MavenRepository.groovy
@@ -16,10 +16,10 @@
 package org.gradle.integtests.fixtures
 
 import java.text.SimpleDateFormat
-import junit.framework.AssertionFailedError
 
 import org.gradle.util.TestFile
 import org.gradle.util.hash.HashUtil
+import groovy.xml.MarkupBuilder
 
 /**
  * A fixture for dealing with Maven repositories.
@@ -48,6 +48,7 @@ class MavenModule {
     final String version
     String parentPomSection
     String type = 'jar'
+    String packaging
     private final List dependencies = []
     int publishCount = 1
     final updateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
@@ -67,8 +68,8 @@ class MavenModule {
         return this
     }
 
-    MavenModule dependsOn(String group, String artifactId, String version) {
-        this.dependencies << [groupId: group, artifactId: artifactId, version: version]
+    MavenModule dependsOn(String group, String artifactId, String version, String type = null) {
+        this.dependencies << [groupId: group, artifactId: artifactId, version: version, type: type]
         return this
     }
 
@@ -94,10 +95,6 @@ class MavenModule {
         return this;
     }
 
-    File getMavenMetaDataFile() {
-        moduleDir.file("maven-metadata.xml")
-    }
-
     /**
      * Asserts that exactly the given artifacts have been deployed, along with their checksum files
      */
@@ -126,11 +123,15 @@ class MavenModule {
     TestFile getPomFile() {
         return moduleDir.file("$artifactId-${publishArtifactVersion}.pom")
     }
-    
+
     TestFile getMetaDataFile() {
         moduleDir.file("maven-metadata.xml")
     }
 
+    TestFile getRootMetaDataFile() {
+        moduleDir.parentFile.file("maven-metadata.xml")
+    }
+
     TestFile getArtifactFile() {
         return artifactFile([:])
     }
@@ -169,11 +170,15 @@ class MavenModule {
      */
     MavenModule publish() {
         moduleDir.createDir()
+        def rootMavenMetaData = getRootMetaDataFile()
 
+        updateRootMavenMetaData(rootMavenMetaData)
         if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
             def metaDataFile = moduleDir.file('maven-metadata.xml')
-            metaDataFile.text = """
+            publish(metaDataFile) {
+                metaDataFile.text = """
 <metadata>
+  <!-- $publishCount -->
   <groupId>$groupId</groupId>
   <artifactId>$artifactId</artifactId>
   <version>$version</version>
@@ -186,37 +191,40 @@ class MavenModule {
   </versioning>
 </metadata>
 """
-            createHashFiles(metaDataFile)
+            }
         }
 
-        pomFile.text = ""
-        pomFile << """
+        publish(pomFile) {
+            def pomPackaging = packaging ?: type;
+            pomFile.text = ""
+            pomFile << """
 <project xmlns="http://maven.apache.org/POM/4.0.0">
   <modelVersion>4.0.0</modelVersion>
   <groupId>$groupId</groupId>
   <artifactId>$artifactId</artifactId>
-  <packaging>$type</packaging>
+  <packaging>$pomPackaging</packaging>
   <version>$version</version>
   <description>Published on $publishTimestamp</description>"""
 
-        if (parentPomSection) {
-            pomFile << "\n$parentPomSection\n"
-        }
+            if (parentPomSection) {
+                pomFile << "\n$parentPomSection\n"
+            }
 
-        dependencies.each { dependency ->
-            pomFile << """
+            dependencies.each { dependency ->
+                def typeAttribute = dependency['type'] == null ? "" : "<type>$dependency.type</type>"
+                pomFile << """
   <dependencies>
     <dependency>
       <groupId>$dependency.groupId</groupId>
       <artifactId>$dependency.artifactId</artifactId>
       <version>$dependency.version</version>
-    </dependency>3.2.1
+      $typeAttribute
+    </dependency>
   </dependencies>"""
-        }
-
-        pomFile << "\n</project>"
+            }
 
-        createHashFiles(pomFile)
+            pomFile << "\n</project>"
+        }
 
         artifacts.each { artifact ->
             publishArtifact(artifact)
@@ -225,15 +233,55 @@ class MavenModule {
         return this
     }
 
+    private void updateRootMavenMetaData(TestFile rootMavenMetaData) {
+        def allVersions = rootMavenMetaData.exists() ? new XmlParser().parseText(rootMavenMetaData.text).versioning.versions.version*.value().flatten() : []
+        allVersions << version;
+        publish(rootMavenMetaData) {
+            rootMavenMetaData.withWriter {writer ->
+                def builder = new MarkupBuilder(writer)
+                builder.metadata {
+                    groupId(groupId)
+                    artifactId(artifactId)
+                    version(allVersions.max())
+                    versioning {
+                        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+                            snapshot {
+                                timestamp(timestampFormat.format(publishTimestamp))
+                                buildNumber(publishCount)
+                                lastUpdated(updateFormat.format(publishTimestamp))
+                            }
+                        } else {
+                            versions {
+                                allVersions.each{currVersion ->
+                                    version(currVersion)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private File publishArtifact(Map<String, ?> artifact) {
         def artifactFile = artifactFile(artifact)
-        if (type != 'pom') {
-            artifactFile << "add some content so that file size isn't zero: $publishCount"
+        publish(artifactFile) {
+            if (type != 'pom') {
+                artifactFile << "add some content so that file size isn't zero: $publishCount"
+            }
         }
-        createHashFiles(artifactFile)
         return artifactFile
     }
 
+    private publish(File file, Closure cl) {
+        def lastModifiedTime = file.exists() ? file.lastModified() : null
+        cl.call(file)
+        if (lastModifiedTime != null) {
+            file.setLastModified(lastModifiedTime + 2000)
+        }
+        createHashFiles(file)
+    }
+
     private Map<String, Object> toArtifact(Map<String, ?> options) {
         options = new HashMap<String, Object>(options)
         def artifact = [type: options.remove('type') ?: type, classifier: options.remove('classifier') ?: null]
@@ -254,8 +302,8 @@ class MavenModule {
         hashFile(file, "md5")
     }
 
-    private TestFile hashFile(File file, String algorithm) {
-        def hashFile = moduleDir.file("${file.name}.${algorithm}")
+    private TestFile hashFile(TestFile file, String algorithm) {
+        def hashFile = file.parentFile.file("${file.name}.${algorithm}")
         hashFile.text = HashUtil.createHash(file, algorithm.toUpperCase()).asHexString()
         return hashFile
     }
@@ -272,6 +320,10 @@ class MavenModule {
         server.expectHead(pomPath(prefix), pomFile)
     }
 
+    public allowPomHead(HttpServer server, prefix = null) {
+        server.allowHead(pomPath(prefix), pomFile)
+    }
+
     public expectPomGet(HttpServer server, prefix = null) {
         server.expectGet(pomPath(prefix), pomFile)
     }
@@ -284,6 +336,10 @@ class MavenModule {
         server.expectGet(pomSha1Path(prefix), sha1File(pomFile))
     }
 
+    public allowPomSha1Get(HttpServer server, prefix = null) {
+        server.allowGet(pomSha1Path(prefix), sha1File(pomFile))
+    }
+
     public pomSha1Path(prefix = null) {
         pomPath(prefix) + ".sha1"
     }
@@ -296,6 +352,10 @@ class MavenModule {
         server.expectGet(artifactPath(prefix), pomFile)
     }
 
+    public allowArtifactHead(HttpServer httpServer, prefix = null) {
+        httpServer.allowHead(artifactPath(prefix), artifactFile)
+    }
+
     public artifactPath(prefix = null) {
         path(prefix, artifactFile.name)
     }
@@ -304,6 +364,10 @@ class MavenModule {
         server.expectGet(artifactSha1Path(prefix), sha1File(artifactFile))
     }
 
+    public allowArtifactSha1Get(HttpServer server, prefix = null) {
+        server.allowGet(artifactSha1Path(prefix), sha1File(artifactFile))
+    }
+
     public artifactSha1Path(prefix = null) {
         artifactPath(prefix) + ".sha1"
     }
@@ -315,10 +379,20 @@ class MavenModule {
 }
 
 class MavenPom {
+    String groupId
+    String artifactId
+    String version
+    String packaging
     final Map<String, MavenScope> scopes = [:]
 
     MavenPom(File pomFile) {
         def pom = new XmlParser().parse(pomFile)
+
+        groupId = pom.groupId[0]?.text()
+        artifactId = pom.artifactId[0]?.text()
+        version = pom.version[0]?.text()
+        packaging = pom.packaging[0]?.text()
+
         pom.dependencies.dependency.each { dep ->
             def scopeElement = dep.scope
             def scopeName = scopeElement ? scopeElement.text() : "runtime"
@@ -347,7 +421,7 @@ class MavenScope {
     void assertDependsOn(String groupId, String artifactId, String version) {
         def dep = [groupId: groupId, artifactId: artifactId, version: version]
         if (!dependencies.find { it == dep }) {
-            throw new AssertionFailedError("Could not find expected dependency $dep. Actual: $dependencies")
+            throw new AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy
index 7cabe19..bc175e4 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/MultiVersionIntegrationSpec.groovy
@@ -20,9 +20,9 @@ import org.junit.runner.RunWith
 
 @RunWith(MultiVersionSpecRunner)
 abstract class MultiVersionIntegrationSpec extends AbstractIntegrationSpec {
-    static def version
+    static String version
 
-    def getVersion() {
-        return version
+    String getVersion() {
+        version
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
index 25cc0b1..bff3729 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/PreviousGradleVersionExecuter.groovy
@@ -23,13 +23,13 @@ import org.gradle.cache.internal.DefaultCacheFactory
 import org.gradle.cache.internal.DefaultFileLockManager
 import org.gradle.cache.internal.DefaultProcessMetaDataProvider
 import org.gradle.cache.internal.FileLockManager.LockMode
+import org.gradle.internal.jvm.Jvm
 import org.gradle.internal.nativeplatform.ProcessEnvironment
 import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.launcher.daemon.registry.DaemonRegistry
 import org.gradle.util.DistributionLocator
 import org.gradle.util.GradleVersion
-import org.gradle.internal.jvm.Jvm
 import org.gradle.util.TestFile
 
 public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implements BasicGradleDistribution {
@@ -72,10 +72,10 @@ public class PreviousGradleVersionExecuter extends AbstractGradleExecuter implem
         }
         // 0.9-rc-1 was broken for Java 5
         if (version == GradleVersion.version('0.9-rc-1')) {
-            return jvm.isJava6Compatible()
+            return jvm.javaVersion.isJava6Compatible()
         }
 
-        return jvm.isJava5Compatible()
+        return jvm.javaVersion.isJava5Compatible()
     }
 
     boolean worksWith(OperatingSystem os) {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy
index 002e561..663598b 100755
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ScriptExecuter.groovy
@@ -15,16 +15,17 @@
  */
 package org.gradle.integtests.fixtures
 
-import org.gradle.process.internal.ExecHandleBuilder
-import org.gradle.process.internal.ExecHandle
-import org.gradle.process.ExecResult
 import org.gradle.internal.os.OperatingSystem
+import org.gradle.process.ExecResult
+import org.gradle.process.internal.ExecHandle
+import org.gradle.process.internal.ExecHandleBuilder
 
 class ScriptExecuter extends ExecHandleBuilder {
     @Override
     ExecHandle build() {
         if (OperatingSystem.current().isWindows()) {
-            args = ['/c', executable.replace('/', File.separator)] + args
+            def theArgs = ['/c', executable.replace('/', File.separator)] + getArgs()
+            setArgs(theArgs) //split purposefully to avoid weird windows CI issue
             executable = 'cmd'
         } else {
             executable = "${workingDir}/${executable}"
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
index 04b498c..ff3e678 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
@@ -40,6 +40,11 @@ public class TestResources implements MethodRule {
     private final Collection<String> extraResources;
     private final Resources resources = new Resources();
 
+    // allows to leave instantiation to Spock
+    public TestResources() {
+        this(new String[0]);
+    }
+
     public TestResources(String... extraResources) {
         this.extraResources = Arrays.asList(extraResources);
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UnexpectedBuildFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UnexpectedBuildFailure.java
new file mode 100644
index 0000000..3593215
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UnexpectedBuildFailure.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.fixtures;
+
+public class UnexpectedBuildFailure extends RuntimeException {
+    public UnexpectedBuildFailure(String message) {
+        super(message);
+    }
+
+    public UnexpectedBuildFailure(Exception e) {
+        super(e);
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/WellBehavedPluginTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/WellBehavedPluginTest.groovy
index 59ce7a0..450e5e0 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/WellBehavedPluginTest.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/WellBehavedPluginTest.groovy
@@ -34,7 +34,7 @@ abstract class WellBehavedPluginTest extends AbstractIntegrationSpec {
 
     def "plugin does not force creation of build dir during configuration"() {
         given:
-        buildFile << "apply plugin: '${getPluginId()}'"
+        applyPlugin()
 
         when:
         run "tasks"
@@ -45,9 +45,13 @@ abstract class WellBehavedPluginTest extends AbstractIntegrationSpec {
 
     def "plugin can build with empty project"() {
         given:
-        buildFile << "apply plugin: '${getPluginId()}'"
+        applyPlugin()
 
         expect:
         succeeds mainTask
     }
+
+    protected applyPlugin(File target = buildFile) {
+        target << "apply plugin: '${getPluginId()}'\n"
+    }
 }
diff --git a/subprojects/internal-testing/internal-testing.gradle b/subprojects/internal-testing/internal-testing.gradle
index ba2dd9f..001ad89 100644
--- a/subprojects/internal-testing/internal-testing.gradle
+++ b/subprojects/internal-testing/internal-testing.gradle
@@ -30,4 +30,5 @@ dependencies {
     compile libraries.junit
     compile libraries.jmock
     compile libraries.spock
-}
+    compile libraries.jsr305 //it is a guava dependency needed for compilation on ibm vm / windows
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/GradlewRunner.java b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/GradlewRunner.java
new file mode 100644
index 0000000..d93c8e5
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/GradlewRunner.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2009 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 org.gradle.testing.internal.util;
+
+import java.io.*;
+
+public class GradlewRunner {
+    public static void main(String[] args) {
+        Process process = null;
+
+        String[] combinedArgs;
+        
+        if (System.getProperty("os.name").startsWith("Windows")) {
+            combinedArgs = new String[3 + args.length];
+            combinedArgs[0] = "cmd";
+            combinedArgs[1] = "/C";
+            combinedArgs[2] = new File("gradlew").getAbsolutePath();
+            System.arraycopy(args, 0, combinedArgs, 3, args.length);
+        } else {
+            combinedArgs = new String[1 + args.length];
+            File gradlew = new File("gradlew");
+            combinedArgs[0] = gradlew.getAbsolutePath();
+            System.arraycopy(args, 0, combinedArgs, 1, args.length);
+        }
+
+        try {
+            ProcessBuilder builder = new ProcessBuilder().command(combinedArgs);
+            process = builder.start();
+            final Process finalProcess = process;
+            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+                public void run() {
+                    finalProcess.destroy();
+                }
+            }));
+            forwardAsync(process.getInputStream(), System.out);
+            forwardAsync(process.getErrorStream(), System.err);
+            process.waitFor();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            process.destroy();
+        }
+    }
+    
+    private static void forwardAsync(final InputStream input, final OutputStream output) {
+        new Thread(new Runnable() {
+            public void run() {
+                int bufferSize = 4096;
+                byte[] buffer = new byte[bufferSize];
+
+                int read = 0;
+                try {
+                    read = input.read(buffer);
+                    while(read != -1) {
+                        output.write(buffer, 0, read);
+                        read = input.read(buffer);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace(new PrintWriter(output));
+                }
+            }
+        }).start();
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/IdeQuickCheckRunner.java b/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/IdeQuickCheckRunner.java
deleted file mode 100644
index 4146918..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/testing/internal/util/IdeQuickCheckRunner.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.testing.internal.util;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-
-public class IdeQuickCheckRunner {
-    public static void main(String[] args) {
-        Process process = null;
-
-        try {
-            ProcessBuilder builder = new ProcessBuilder().command("./gradlew", "quickCheck", "--daemon");
-            process = builder.start();
-            final Process finalProcess = process;
-            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
-                public void run() {
-                    finalProcess.destroy();
-                }
-            }));
-            forwardAsync(process.getInputStream(), System.out);
-            forwardAsync(process.getErrorStream(), System.err);
-            process.waitFor();
-        } catch (IOException e) {
-            e.printStackTrace();
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-            process.destroy();
-        }
-    }
-    
-    private static void forwardAsync(final InputStream input, final OutputStream output) {
-        new Thread(new Runnable() {
-            public void run() {
-                int bufferSize = 4096;
-                byte[] buffer = new byte[bufferSize];
-
-                int read = 0;
-                try {
-                    read = input.read(buffer);
-                    while(read != -1) {
-                        output.write(buffer, 0, read);
-                        read = input.read(buffer);
-                    }
-                } catch (IOException e) {
-                    e.printStackTrace(new PrintWriter(output));
-                }
-            }
-        }).start();
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/DynamicDelegate.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/DynamicDelegate.groovy
new file mode 100644
index 0000000..de3c188
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/DynamicDelegate.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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 org.gradle.util
+
+/**
+ * Intended to be used in tests to focus on a dynamic object…
+ *
+ * <pre>
+ * \@Delegate DynamicDelegate delegate = new DynamicDelegate(someObject)
+ * </pre>
+ */
+class DynamicDelegate {
+
+    private final delegate
+
+    DynamicDelegate(delegate) {
+        this.delegate = delegate
+    }
+
+    def methodMissing(String name, args) {
+        delegate."$name"(*args)
+    }
+
+    def propertyMissing(String name) {
+        delegate."$name"
+    }
+
+    def propertyMissing(String name, value) {
+        delegate."$name" = value
+    }
+
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy
index a46ac9f..4d614b5 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestDirHelper.groovy
@@ -34,6 +34,10 @@ class TestDirHelper {
         file
     }
 
+    def setMode(int mode) {
+        baseDir.mode = mode
+    }
+
     def methodMissing(String name, Object args) {
         if (args.length == 1 && args[0] instanceof Closure) {
             baseDir.file(name).create(args[0])
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
index ee8c154..44dc2c5 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFile.java
@@ -128,6 +128,16 @@ public class TestFile extends File implements TestFileContext {
         }
     }
 
+    public TestFile[] listFiles() {
+        File[] children = super.listFiles();
+        TestFile[] files = new TestFile[children.length];
+        for (int i = 0; i < children.length; i++) {
+            File child = children[i];
+            files[i] = new TestFile(child);
+        }
+        return files;
+    }
+
     public String getText() {
         assertIsFile();
         try {
@@ -351,6 +361,12 @@ public class TestFile extends File implements TestFileContext {
         return this;
     }
 
+    public TestFile setMode(int mode) {
+        assertExists();
+        new TestFileHelper(this).setMode(mode);
+        return this;
+    }
+
     public int getMode() {
         assertExists();
         return new TestFileHelper(this).getMode();
@@ -416,7 +432,7 @@ public class TestFile extends File implements TestFileContext {
     }
 
     public TestFile deleteDir() {
-        FileUtils.deleteQuietly(this);
+        new TestFileHelper(this).delete(useNativeTools);
         return this;
     }
 
@@ -458,19 +474,13 @@ public class TestFile extends File implements TestFileContext {
         return zipFile;
     }
 
-    public TestFile zipTo(TestFile zipFile) {
-        Zip zip = new Zip();
-        zip.setBasedir(this);
-        zip.setDestFile(zipFile);
-        execute(zip);
+    public TestFile zipTo(TestFile zipFile){
+        new TestFileHelper(this).zipTo(zipFile, useNativeTools);
         return this;
     }
 
-    public TestFile tarTo(TestFile zipFile) {
-        Tar tar = new Tar();
-        tar.setBasedir(this);
-        tar.setDestFile(zipFile);
-        execute(tar);
+    public TestFile tarTo(TestFile tarFile) {
+        new TestFileHelper(this).tarTo(tarFile, useNativeTools);
         return this;
     }
 
@@ -499,7 +509,7 @@ public class TestFile extends File implements TestFileContext {
 
     public Snapshot snapshot() {
         assertIsFile();
-        return new Snapshot();
+        return new Snapshot(lastModified(), getHash("MD5"));
     }
 
     public void assertHasChangedSince(Snapshot snapshot) {
@@ -541,13 +551,9 @@ public class TestFile extends File implements TestFileContext {
         private final long modTime;
         private final byte[] hash;
 
-        public Snapshot() {
-            modTime = lastModified();
-            hash = getHash("MD5");
-        }
-
-        public long lastModified() {
-            return modTime;
+        public Snapshot(long modTime, byte[] hash) {
+            this.modTime = modTime;
+            this.hash = hash;
         }
     }
 }
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
index 7f56b7c..6320835 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestFileHelper.groovy
@@ -17,12 +17,15 @@ package org.gradle.util
 
 import java.util.zip.ZipInputStream
 import org.apache.commons.lang.StringUtils
+import org.apache.tools.ant.Project
 import org.apache.tools.ant.taskdefs.Expand
+import org.apache.tools.ant.taskdefs.Tar
 import org.apache.tools.ant.taskdefs.Untar
+import org.apache.tools.ant.taskdefs.Zip
 import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
 import static org.junit.Assert.assertTrue
-import org.apache.tools.ant.Project
+import org.apache.commons.io.FileUtils
 
 class TestFileHelper {
     TestFile file
@@ -67,7 +70,7 @@ class TestFileHelper {
     void untarTo(File target, boolean nativeTools) {
         if (nativeTools && isUnix()) {
             target.mkdirs()
-            def builder = new ProcessBuilder(['tar', '-xf', file.absolutePath])
+            def builder = new ProcessBuilder(['tar', '-xpf', file.absolutePath])
             builder.directory(target)
             def process = builder.start()
             process.consumeProcessOutput()
@@ -111,8 +114,15 @@ class TestFileHelper {
     }
 
     void setPermissions(String permissions) {
+        if (!isUnix()) {
+            return
+        }
         int m = toMode(permissions)
-        def process = ["chmod", Integer.toOctalString(m), file.absolutePath].execute()
+        setMode(m)
+    }
+
+    void setMode(int mode) {
+        def process = ["chmod", Integer.toOctalString(mode), file.absolutePath].execute()
         def error = process.errorStream.text
         def retval = process.waitFor()
         if (retval != 0) {
@@ -134,12 +144,25 @@ class TestFileHelper {
         return toMode(getPermissions())
     }
 
+    void delete(boolean nativeTools) {
+        if (isUnix() && nativeTools) {
+            def process = ["rm", "-rf", file.absolutePath].execute()
+            def error = process.errorStream.text
+            def retval = process.waitFor()
+            if (retval != 0) {
+                throw new RuntimeException("Could not delete '$file': $error")
+            }
+        } else {
+            FileUtils.deleteQuietly(file);
+        }
+    }
+
     String readLink() {
         def process = ["readlink", file.absolutePath].execute()
         def error = process.errorStream.text
         def retval = process.waitFor()
         if (retval != 0) {
-            throw new RuntimeException("Could not set permissions for '$file': $error")
+            throw new RuntimeException("Could not read link '$file': $error")
         }
         return process.inputStream.text.trim()
     }
@@ -153,4 +176,32 @@ class TestFileHelper {
         }
         return [out: output, error: error]
     }
+
+    public void zipTo(TestFile zipFile, boolean nativeTools) {
+        if (nativeTools && isUnix()) {
+            def process = ['zip', zipFile.absolutePath, "-r", file.name].execute(null, zipFile.parentFile)
+            process.consumeProcessOutput(System.out, System.err)
+            assertThat(process.waitFor(), equalTo(0))
+        } else {
+            Zip zip = new Zip();
+            zip.setBasedir(file);
+            zip.setDestFile(zipFile);
+            zip.setProject(new Project());
+            zip.execute();
+        }
+    }
+
+    public void tarTo(TestFile tarFile, boolean nativeTools) {
+        if (nativeTools && isUnix()) {
+            def process = ['tar', "-cf", tarFile.absolutePath, file.name].execute(null, tarFile.parentFile)
+            process.consumeProcessOutput(System.out, System.err)
+            assertThat(process.waitFor(), equalTo(0))
+        } else {
+            Tar tar = new Tar();
+            tar.setBasedir(file);
+            tar.setDestFile(tarFile);
+            tar.setProject(new Project())
+            tar.execute()
+        }
+    }
 }
\ No newline at end of file
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
index 7e9b839..980b7bc 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
@@ -54,6 +54,9 @@ enum TestPrecondition {
     NO_FILE_LOCK_ON_OPEN({
         MAC_OS_X.fulfilled || LINUX.fulfilled
     }),
+    MANDATORY_FILE_LOCKING({
+        OperatingSystem.current().windows
+    }),
     WINDOWS({
         OperatingSystem.current().windows
     }),
diff --git a/subprojects/javascript/javascript.gradle b/subprojects/javascript/javascript.gradle
new file mode 100644
index 0000000..a0566bb
--- /dev/null
+++ b/subprojects/javascript/javascript.gradle
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+
+    compile "org.mozilla:rhino:1.7R3"
+    compile "com.google.code.gson:gson:2.2.1" // used by JsHint
+    compile "org.simpleframework:simple:4.1.21" // used by http package in envjs
+    compile project(':core'), project(":plugins")
+
+    // Required by JavaScriptExtension#getGoogleApisRepository()
+    compile project(':coreImpl')
+}
+
+useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginIntegrationTest.groovy b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginIntegrationTest.groovy
new file mode 100644
index 0000000..e6192ff
--- /dev/null
+++ b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginIntegrationTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.base
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+import static org.gradle.plugins.javascript.base.JavaScriptBasePluginTestFixtures.addGoogleRepoScript
+
+class JavaScriptBasePluginIntegrationTest extends WellBehavedPluginTest {
+
+    @Override
+    String getPluginId() {
+        "javascript-base"
+    }
+
+    def setup() {
+        applyPlugin()
+    }
+
+    def "can download from googles repo"() {
+        given:
+        addGoogleRepoScript(buildFile)
+
+        when:
+        buildFile << """
+            configurations {
+                jquery
+            }
+            dependencies {
+                jquery "jquery:jquery.min:1.7.2 at js"
+            }
+            task resolve(type: Copy) {
+                from configurations.jquery
+                into "jquery"
+            }
+        """
+
+        then:
+        succeeds "resolve"
+
+        and:
+        def jquery = file("jquery/jquery.min-1.7.2.js")
+        jquery.exists()
+        jquery.text.contains("jQuery v1.7.2")
+
+    }
+
+}
diff --git a/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginIntegrationTest.groovy b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginIntegrationTest.groovy
new file mode 100644
index 0000000..4d1ed60
--- /dev/null
+++ b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginIntegrationTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+import static org.gradle.plugins.javascript.base.JavaScriptBasePluginTestFixtures.*
+import static org.gradle.plugins.javascript.coffeescript.CoffeeScriptBasePluginTestFixtures.*
+
+class CoffeeScriptBasePluginIntegrationTest extends WellBehavedPluginTest {
+
+    @Override
+    String getPluginId() {
+        "coffeescript-base"
+    }
+
+    def setup() {
+        addApplyPluginScript(buildFile)
+        addGradlePublicJsRepoScript(buildFile)
+    }
+
+    def "can download coffeescript by default"() {
+        given:
+        buildFile << """
+            task resolve(type: Copy) {
+                from javaScript.coffeeScript.js
+                into "deps"
+            }
+        """
+
+        when:
+        run "resolve"
+
+        then:
+        def js = file("deps/coffee-script-js-1.3.3.js")
+        js.exists()
+        js.text.contains("CoffeeScript Compiler")
+    }
+
+    def "can compile coffeescript"() {
+        given:
+        file("src/main/coffeescript/dir1/thing1.coffee") << "number = 1"
+        file("src/main/coffeescript/dir2/thing2.coffee") << "number = 2"
+
+        buildFile << """
+            repositories.mavenCentral()
+            task compile(type: ${CoffeeScriptCompile.name}) {
+                destinationDir file("build/compiled/js")
+                source fileTree("src/main/coffeescript")
+            }
+        """
+
+        when:
+        run "compile"
+
+        then:
+        ":compile" in nonSkippedTasks
+
+        and:
+        def f1 = file("build/compiled/js/dir1/thing1.js")
+        f1.exists()
+        f1.text.startsWith("(function() {")
+
+        def f2 = file("build/compiled/js/dir2/thing2.js")
+        f2.exists()
+        f2.text.startsWith("(function() {")
+
+        when:
+        run "compile"
+
+        then:
+        ":compile" in skippedTasks
+    }
+}
diff --git a/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/envjs/EnvJsPluginIntegrationTest.groovy b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/envjs/EnvJsPluginIntegrationTest.groovy
new file mode 100644
index 0000000..e6f37d2
--- /dev/null
+++ b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/envjs/EnvJsPluginIntegrationTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+import static org.gradle.plugins.javascript.base.JavaScriptBasePluginTestFixtures.addGradlePublicJsRepoScript
+import org.gradle.plugins.javascript.envjs.browser.BrowserEvaluate
+
+import static org.gradle.plugins.javascript.base.JavaScriptBasePluginTestFixtures.addGoogleRepoScript
+
+class EnvJsPluginIntegrationTest extends WellBehavedPluginTest {
+
+    def setup() {
+        applyPlugin()
+        addGradlePublicJsRepoScript(buildFile)
+        buildFile << """
+            repositories.mavenCentral()
+        """
+    }
+
+    def "can download envjs by default"() {
+        given:
+        buildFile << """
+            task resolve(type: Copy) {
+                from javaScript.envJs.js
+                into "deps"
+            }
+        """
+
+        when:
+        run "resolve"
+
+        then:
+        def js = file("deps/envjs.rhino-1.2.js")
+        js.exists()
+        js.text.contains("Envjs = function")
+    }
+
+    def "can evaluate content"() {
+        given:
+        file("input/index.html") << """
+            <html>
+                <head>
+                    <script src="\${jqueryFileName}" type="text/javascript"></script>
+                    <script type="text/javascript">
+                        \\\$(function() {
+                            \\\$("body").text("Added!");
+                        });
+                    </script>
+                </head>
+            </html>
+        """
+
+        addGoogleRepoScript(buildFile)
+
+        buildFile << """
+            configurations {
+                jquery
+            }
+            dependencies {
+                jquery "jquery:jquery.min:1.7.2 at js"
+            }
+
+            task gatherContent(type: Copy) {
+                into "content"
+                from configurations.jquery
+                from "input", {
+                    expand jqueryFileName: "\${->configurations.jquery.singleFile.name}"
+                }
+            }
+
+            task evaluate(type: ${BrowserEvaluate.name}) {
+                content gatherContent
+                resource "index.html"
+                result "result.html"
+            }
+        """
+
+        when:
+        succeeds "evaluate"
+
+        then:
+        file("result.html").text.contains("<body>Added!</body>")
+    }
+}
diff --git a/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/jshint/JsHintPluginIntegrationTest.groovy b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/jshint/JsHintPluginIntegrationTest.groovy
new file mode 100644
index 0000000..99f2515
--- /dev/null
+++ b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/jshint/JsHintPluginIntegrationTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.jshint
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+import static org.gradle.plugins.javascript.base.JavaScriptBasePluginTestFixtures.addGradlePublicJsRepoScript
+import groovy.json.JsonSlurper
+
+class JsHintPluginIntegrationTest extends WellBehavedPluginTest {
+
+    def setup() {
+        applyPlugin()
+        addGradlePublicJsRepoScript(buildFile)
+        buildFile << """
+            repositories.mavenCentral()
+        """
+    }
+
+    def taskForFileTree(String path = "src/main/js", File file = buildFile) {
+        file << """
+            task jsHint(type: ${JsHint.name}) {
+                source fileTree("$path")
+            }
+        """
+    }
+
+    def "can analyse bad javascript"() {
+        given:
+        file("src/main/js/dir1/f1.js") << """
+            "a" == null
+        """
+        file("src/main/js/dir2/f2.js") << """
+            "b" == null
+        """
+
+        when:
+        taskForFileTree()
+
+        then:
+        fails "jsHint"
+
+        and:
+        failureHasCause "JsHint detected errors"
+
+        and:
+        output.contains "2:17 > Use '===' to compare with 'null'."
+
+        and:
+        File jsonReport = file("build/reports/jsHint/report.json")
+        jsonReport.exists()
+
+        and: // it's valid json
+        def json = new JsonSlurper().parseText(jsonReport.text)
+        json[file("src/main/js/dir1/f1.js").absolutePath] instanceof Map
+    }
+
+    def "can analyse good javascript"() {
+        given:
+        file("src/main/js/dir1/f1.js") << """
+            var a = "a" === null;
+        """
+        file("src/main/js/dir2/f2.js") << """
+            var b = "b" === null;
+        """
+
+        when:
+        taskForFileTree()
+
+        then:
+        succeeds "jsHint"
+
+        and:
+        ":jsHint" in nonSkippedTasks
+
+        and:
+        File jsonReport = file("build/reports/jsHint/report.json")
+        jsonReport.exists()
+
+        and: // it's valid json
+        def json = new JsonSlurper().parseText(jsonReport.text)
+        json[file("src/main/js/dir1/f1.js").absolutePath] instanceof Map
+
+        when:
+        run "jsHint"
+
+        then:
+        ":jsHint" in skippedTasks
+    }
+
+}
diff --git a/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/rhino/RhinoPluginIntegrationTest.groovy b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/rhino/RhinoPluginIntegrationTest.groovy
new file mode 100644
index 0000000..92645ec
--- /dev/null
+++ b/subprojects/javascript/src/integTest/groovy/org/gradle/plugins/javascript/rhino/RhinoPluginIntegrationTest.groovy
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class RhinoPluginIntegrationTest extends WellBehavedPluginTest {
+
+    def setup() {
+        applyPlugin()
+
+        buildFile << """
+            repositories {
+                mavenCentral()
+            }
+        """
+    }
+
+
+    def "can use default rhino dependency"() {
+        when:
+        buildFile << """
+            task resolve(type: Copy) {
+                from javaScript.rhino.classpath
+                into "deps"
+            }
+        """
+
+        then:
+        succeeds("resolve")
+
+        and:
+        file("deps/rhino-${RhinoExtension.DEFAULT_RHINO_DEPENDENCY_VERSION}.jar").exists()
+    }
+
+    def "can run rhino exec task"() {
+        given:
+        file("some.js") << """
+            print("rhino js-version: " + version())
+            print("rhino arg: " + arguments[0])
+        """
+
+        buildFile << """
+            task rhino(type: ${RhinoShellExec.name}) {
+                rhinoOptions "-version", "160"
+                script "some.js"
+                scriptArgs "foo"
+            }
+        """
+
+        when:
+        run "rhino"
+
+        then:
+        output.contains "rhino js-version: 160"
+        output.contains "rhino arg: foo"
+    }
+
+    def "compile failure fails task"() {
+        given:
+        file("some.js") << " ' "
+
+        buildFile << """
+            task rhino(type: ${RhinoShellExec.name}) {
+                script "some.js"
+            }
+        """
+
+        expect:
+        fails "rhino"
+    }
+
+    def "can use older rhino version"() {
+        given:
+        buildFile << """
+            dependencies {
+                it.${RhinoExtension.CLASSPATH_CONFIGURATION_NAME} "rhino:js:1.6R6"
+            }
+
+            task rhino(type: ${RhinoShellExec.name}) {
+                rhinoOptions "-e", "print('rhinoClasspath: ' + environment['java.class.path'])"
+            }
+        """
+
+        when:
+        run "rhino"
+
+        then:
+        output.readLines().any { it ==~ /rhinoClasspath:.+js-1.6R6.jar/ }
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy
new file mode 100644
index 0000000..d8375b8
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePlugin.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.base
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.internal.artifacts.DependencyResolutionServices
+import org.gradle.api.internal.artifacts.ResolverFactory
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.BasePlugin
+
+class JavaScriptBasePlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        ProjectInternal projectInternal = project as ProjectInternal
+
+        project.apply(plugin: BasePlugin)
+        ResolverFactory resolverFactory = projectInternal.services.get(DependencyResolutionServices).resolverFactory
+        JavaScriptExtension extension = project.extensions.create(JavaScriptExtension.NAME, JavaScriptExtension, resolverFactory)
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java
new file mode 100644
index 0000000..dea76fd
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/base/JavaScriptExtension.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.base;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.internal.artifacts.ResolverFactory;
+import org.gradle.api.internal.artifacts.repositories.layout.PatternRepositoryLayout;
+
+public class JavaScriptExtension {
+
+    public static final String NAME = "javaScript";
+
+    public static final String GRADLE_PUBLIC_JAVASCRIPT_REPO_URL = "http://repo.gradle.org/gradle/javascript-public";
+    public static final String GOOGLE_APIS_REPO_URL = "http://ajax.googleapis.com/ajax/libs";
+
+    private final ResolverFactory resolverFactory;
+
+    public JavaScriptExtension(ResolverFactory resolverFactory) {
+        this.resolverFactory = resolverFactory;
+    }
+
+    public ArtifactRepository getGradlePublicJavaScriptRepository() {
+        MavenArtifactRepository repo = resolverFactory.createMavenRepository();
+        repo.setUrl(GRADLE_PUBLIC_JAVASCRIPT_REPO_URL);
+        repo.setName("Gradle Public JavaScript Repository");
+        return repo;
+    }
+
+    public ArtifactRepository getGoogleApisRepository() {
+        IvyArtifactRepository repo = resolverFactory.createIvyRepository();
+        repo.setName("Google Libraries Repository");
+        repo.setUrl(GOOGLE_APIS_REPO_URL);
+        repo.layout("pattern", new Closure(this) {
+            public void doCall() {
+                PatternRepositoryLayout layout = (PatternRepositoryLayout) getDelegate();
+                layout.artifact("[organization]/[revision]/[module].[ext]");
+                layout.ivy("[organization]/[revision]/[module].xml");
+            }
+        });
+        return repo;
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePlugin.groovy
new file mode 100644
index 0000000..a20e82a
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePlugin.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript
+
+import org.gradle.api.Action
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ResolvableDependencies
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.plugins.javascript.base.JavaScriptExtension
+import org.gradle.plugins.javascript.rhino.RhinoExtension
+
+import static org.gradle.plugins.javascript.coffeescript.CoffeeScriptExtension.*
+
+class CoffeeScriptBasePlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.apply(plugin: "rhino")
+
+        JavaScriptExtension jsExtension = project.extensions.getByType(JavaScriptExtension)
+        CoffeeScriptExtension csExtension = jsExtension.extensions.create(CoffeeScriptExtension.NAME, CoffeeScriptExtension)
+        Configuration jsConfiguration = addJsConfiguration(project.configurations, project.dependencies, csExtension)
+
+        csExtension.conventionMapping.with {
+            map("js") { jsConfiguration }
+            map("version") { DEFAULT_JS_DEPENDENCY_VERSION }
+        }
+
+        RhinoExtension rhinoExtension = jsExtension.extensions.getByType(RhinoExtension)
+
+        project.tasks.withType(CoffeeScriptCompile) { CoffeeScriptCompile task ->
+            task.conventionMapping.map("rhinoClasspath") { rhinoExtension.classpath }
+            task.conventionMapping.map("coffeeScriptJs") { csExtension.js }
+        }
+    }
+
+    private Configuration addJsConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, CoffeeScriptExtension extension) {
+        Configuration configuration = configurations.add(CoffeeScriptExtension.JS_CONFIGURATION_NAME)
+        configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
+            void execute(ResolvableDependencies resolvableDependencies) {
+                if (configuration.dependencies.empty) {
+                    String notation = "${DEFAULT_JS_DEPENDENCY_GROUP}:${DEFAULT_JS_DEPENDENCY_MODULE}:${extension.version}@js"
+                    Dependency dependency = dependencies.create(notation)
+                    configuration.dependencies.add(dependency)
+                }
+            }
+        })
+        configuration
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompile.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompile.java
new file mode 100644
index 0000000..93cd900
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompile.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.SourceTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.internal.Factory;
+import org.gradle.plugins.javascript.coffeescript.compile.internal.DefaultCoffeeScriptCompileSpec;
+import org.gradle.plugins.javascript.coffeescript.compile.internal.rhino.RhinoCoffeeScriptCompiler;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory;
+import org.gradle.plugins.javascript.rhino.worker.internal.DefaultRhinoWorkerHandleFactory;
+import org.gradle.process.internal.WorkerProcessBuilder;
+
+import java.io.File;
+
+public class CoffeeScriptCompile extends SourceTask {
+
+    private Object coffeeScriptJs;
+    private Object destinationDir;
+    private Object rhinoClasspath;
+    private CoffeeScriptCompileOptions options = new CoffeeScriptCompileOptions();
+
+    @InputFiles
+    public FileCollection getCoffeeScriptJs() {
+        return getProject().files(coffeeScriptJs);
+    }
+
+    public void setCoffeeScriptJs(Object coffeeScriptJs) {
+        this.coffeeScriptJs = coffeeScriptJs;
+    }
+
+    @OutputDirectory
+    public File getDestinationDir() {
+        return getProject().file(destinationDir);
+    }
+
+    public void setDestinationDir(Object destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    @InputFiles
+    public FileCollection getRhinoClasspath() {
+        return getProject().files(rhinoClasspath);
+    }
+
+    public void setRhinoClasspath(Object rhinoClasspath) {
+        this.rhinoClasspath = rhinoClasspath;
+    }
+
+    public CoffeeScriptCompileOptions getOptions() {
+        return options;
+    }
+
+    public void options(Action<CoffeeScriptCompileOptions> action) {
+        action.execute(getOptions());
+    }
+
+    public void options(Closure<?> closure) {
+        getProject().configure(getOptions(), closure);
+    }
+
+    @TaskAction
+    public void doCompile() {
+        ProjectInternal projectInternal = (ProjectInternal)getProject();
+        Factory<WorkerProcessBuilder> workerProcessBuilderFactory = projectInternal.getServices().getFactory(WorkerProcessBuilder.class);
+        RhinoWorkerHandleFactory handleFactory = new DefaultRhinoWorkerHandleFactory(workerProcessBuilderFactory);
+
+        CoffeeScriptCompileSpec spec = new DefaultCoffeeScriptCompileSpec();
+        spec.setCoffeeScriptJs(getCoffeeScriptJs().getSingleFile());
+        spec.setDestinationDir(getDestinationDir());
+        spec.setSource(getSource());
+        spec.setOptions(getOptions());
+
+        LogLevel logLevel = getProject().getGradle().getStartParameter().getLogLevel();
+        CoffeeScriptCompiler compiler = new RhinoCoffeeScriptCompiler(handleFactory, getRhinoClasspath(), logLevel, getProject().getProjectDir());
+
+        setDidWork(compiler.compile(spec).getDidWork());
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompileOptions.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompileOptions.java
new file mode 100644
index 0000000..3897104
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompileOptions.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript;
+
+import java.io.Serializable;
+
+public class CoffeeScriptCompileOptions implements Serializable {
+
+    private String encoding = "UTF-8";
+
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompileSpec.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompileSpec.java
new file mode 100644
index 0000000..f36570e
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompileSpec.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript;
+
+import org.gradle.api.file.FileCollection;
+
+import java.io.File;
+
+public interface CoffeeScriptCompileSpec {
+
+    File getCoffeeScriptJs();
+
+    void setCoffeeScriptJs(File coffeeScriptJs);
+
+    File getDestinationDir();
+
+    void setDestinationDir(File destinationDir);
+
+    FileCollection getSource();
+
+    void setSource(FileCollection source);
+
+    CoffeeScriptCompileOptions getOptions();
+
+    void setOptions(CoffeeScriptCompileOptions options);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompiler.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompiler.java
new file mode 100644
index 0000000..d878638
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptCompiler.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript;
+
+import org.gradle.api.tasks.WorkResult;
+
+public interface CoffeeScriptCompiler {
+
+    WorkResult compile(CoffeeScriptCompileSpec spec);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptExtension.java
new file mode 100644
index 0000000..1276d47
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptExtension.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript;
+
+import org.gradle.api.file.FileCollection;
+
+public class CoffeeScriptExtension {
+
+    public static final String NAME = "coffeeScript";
+
+    public static final String DEFAULT_JS_DEPENDENCY_VERSION = "1.3.3";
+    public static final String DEFAULT_JS_DEPENDENCY_GROUP = "org.coffeescript";
+    public static final String DEFAULT_JS_DEPENDENCY_MODULE = "coffee-script-js";
+
+    public static final String JS_CONFIGURATION_NAME = "coffeeScriptBasePluginJs";
+
+    private FileCollection js;
+    private String version;
+
+    public FileCollection getJs() {
+        return js;
+    }
+
+    public void setJs(FileCollection js) {
+        this.js = js;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/CoffeeScriptCompileDestinationCalculator.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/CoffeeScriptCompileDestinationCalculator.java
new file mode 100644
index 0000000..505f177
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/CoffeeScriptCompileDestinationCalculator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript.compile.internal;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.file.RelativePath;
+
+import java.io.File;
+
+public class CoffeeScriptCompileDestinationCalculator implements Transformer<File, RelativePath> {
+
+    private final File destination;
+
+    public CoffeeScriptCompileDestinationCalculator(File destination) {
+        this.destination = destination;
+    }
+
+    public File transform(RelativePath relativePath) {
+        String sourceFileName = relativePath.getLastName();
+        
+        String destinationFileNameBase = sourceFileName;
+        if (sourceFileName.endsWith(".coffee")) {
+            destinationFileNameBase = sourceFileName.substring(0, sourceFileName.length() - 7);
+        }
+        
+        String destinationFileName = String.format("%s.js", destinationFileNameBase);
+        RelativePath destinationRelativePath = relativePath.replaceLastName(destinationFileName);
+        return new File(destination, destinationRelativePath.getPathString());
+    }
+
+    public static Transformer<Transformer<File, RelativePath>, File> asFactory() {
+        return new Transformer<Transformer<File, RelativePath>, File>() {
+            public Transformer<File, RelativePath> transform(File original) {
+                return new CoffeeScriptCompileDestinationCalculator(original);
+            }
+        };
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/CoffeeScriptCompileResult.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/CoffeeScriptCompileResult.java
new file mode 100644
index 0000000..acb71fb
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/CoffeeScriptCompileResult.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript.compile.internal;
+
+import org.gradle.api.tasks.WorkResult;
+
+public class CoffeeScriptCompileResult implements WorkResult {
+
+    private final boolean didWork;
+
+    public CoffeeScriptCompileResult(boolean didWork) {
+        this.didWork = didWork;
+    }
+
+    public boolean getDidWork() {
+        return didWork;
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/DefaultCoffeeScriptCompileSpec.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/DefaultCoffeeScriptCompileSpec.java
new file mode 100644
index 0000000..4261cea
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/DefaultCoffeeScriptCompileSpec.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript.compile.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.plugins.javascript.coffeescript.CoffeeScriptCompileOptions;
+import org.gradle.plugins.javascript.coffeescript.CoffeeScriptCompileSpec;
+
+import java.io.File;
+
+public class DefaultCoffeeScriptCompileSpec implements CoffeeScriptCompileSpec {
+
+    private File coffeeScriptJs;
+    private File destinationDir;
+    private FileCollection source;
+    private CoffeeScriptCompileOptions options;
+
+    public File getCoffeeScriptJs() {
+        return coffeeScriptJs;
+    }
+
+    public void setCoffeeScriptJs(File coffeeScriptJs) {
+        this.coffeeScriptJs = coffeeScriptJs;
+    }
+
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    public FileCollection getSource() {
+        return source;
+    }
+
+    public void setSource(FileCollection source) {
+        this.source = source;
+    }
+
+    public CoffeeScriptCompileOptions getOptions() {
+        return options;
+    }
+
+    public void setOptions(CoffeeScriptCompileOptions options) {
+        this.options = options;
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/SerializableCoffeeScriptCompileSpec.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/SerializableCoffeeScriptCompileSpec.java
new file mode 100644
index 0000000..5043479
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/SerializableCoffeeScriptCompileSpec.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript.compile.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.internal.file.RelativeFile;
+import org.gradle.plugins.javascript.coffeescript.CoffeeScriptCompileOptions;
+import org.gradle.plugins.javascript.coffeescript.CoffeeScriptCompileSpec;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+public class SerializableCoffeeScriptCompileSpec implements Serializable {
+
+    private final File coffeeScriptJs;
+    private final File destinationDir;
+    private final List<RelativeFile> source;
+    private final CoffeeScriptCompileOptions options;
+
+    public SerializableCoffeeScriptCompileSpec(CoffeeScriptCompileSpec spec) {
+        this(spec.getCoffeeScriptJs(), spec.getDestinationDir(), spec.getSource(), spec.getOptions());
+    }
+    public SerializableCoffeeScriptCompileSpec(File coffeeScriptJs, File destinationDir, FileCollection source, CoffeeScriptCompileOptions options) {
+        this.coffeeScriptJs = coffeeScriptJs;
+        this.destinationDir = destinationDir;
+        this.source = new LinkedList<RelativeFile>();
+        this.options = options;
+
+        toRelativeFiles(source, this.source);
+    }
+
+    public static void toRelativeFiles(final FileCollection source, final List<RelativeFile> targets) {
+        FileTree fileTree = source.getAsFileTree();
+
+        fileTree.visit(new FileVisitor() {
+            public void visitDir(FileVisitDetails dirDetails) {}
+
+            public void visitFile(FileVisitDetails fileDetails) {
+                targets.add(new RelativeFile(fileDetails.getFile(), fileDetails.getRelativePath()));
+            }
+        });
+    }
+
+    public File getCoffeeScriptJs() {
+        return coffeeScriptJs;
+    }
+
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    public List<RelativeFile> getSource() {
+        return source;
+    }
+
+    public CoffeeScriptCompileOptions getOptions() {
+        return options;
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/rhino/CoffeeScriptCompilerWorker.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/rhino/CoffeeScriptCompilerWorker.java
new file mode 100644
index 0000000..3a7e6ab
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/rhino/CoffeeScriptCompilerWorker.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript.compile.internal.rhino;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.file.RelativeFile;
+import org.gradle.plugins.javascript.coffeescript.compile.internal.CoffeeScriptCompileDestinationCalculator;
+import org.gradle.plugins.javascript.coffeescript.compile.internal.SerializableCoffeeScriptCompileSpec;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorker;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.RhinoException;
+import org.mozilla.javascript.Scriptable;
+
+import static org.gradle.plugins.javascript.rhino.worker.RhinoWorkerUtils.*;
+
+public class CoffeeScriptCompilerWorker implements RhinoWorker<Boolean, SerializableCoffeeScriptCompileSpec> {
+
+    public Boolean process(SerializableCoffeeScriptCompileSpec spec) {
+        Scriptable coffeeScriptScope = parse(spec.getCoffeeScriptJs(), "UTF-8", new Action<Context>() {
+            public void execute(Context context) {
+                context.setOptimizationLevel(-1);
+            }
+        });
+
+        String encoding = spec.getOptions().getEncoding();
+
+        CoffeeScriptCompileDestinationCalculator destinationCalculator = new CoffeeScriptCompileDestinationCalculator(spec.getDestinationDir());
+
+        for (RelativeFile target : spec.getSource()) {
+            String source = readFile(target.getFile(), encoding);
+            String output = compile(coffeeScriptScope, source, target.getRelativePath().getPathString());
+            writeFile(output, destinationCalculator.transform(target.getRelativePath()), encoding);
+        }
+
+        return Boolean.TRUE;
+    }
+
+    public Exception convertException(RhinoException rhinoException) {
+        // TODO - need to convert this to a non rhino type in case the version is different back at the client
+        return rhinoException;
+    }
+
+    private String compile(Scriptable rootScope, final String source, final String sourceName) {
+        return childScope(rootScope, new DefaultScopeOperation<String>() {
+            public String action(Scriptable compileScope, Context context) {
+                compileScope.put("coffeeScriptSource", compileScope, source);
+                return (String)context.evaluateString(compileScope, "CoffeeScript.compile(coffeeScriptSource, {});", sourceName, 0, null);
+            }
+        });
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/rhino/RhinoCoffeeScriptCompiler.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/rhino/RhinoCoffeeScriptCompiler.java
new file mode 100644
index 0000000..04147ae
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/coffeescript/compile/internal/rhino/RhinoCoffeeScriptCompiler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript.compile.internal.rhino;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.plugins.javascript.coffeescript.CoffeeScriptCompileSpec;
+import org.gradle.plugins.javascript.coffeescript.CoffeeScriptCompiler;
+import org.gradle.plugins.javascript.coffeescript.compile.internal.SerializableCoffeeScriptCompileSpec;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandle;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerSpec;
+import org.gradle.process.JavaExecSpec;
+
+import java.io.File;
+
+public class RhinoCoffeeScriptCompiler implements CoffeeScriptCompiler {
+
+    private final RhinoWorkerHandleFactory rhinoWorkerHandleFactory;
+    private final Iterable<File> rhinoClasspath;
+    private final LogLevel logLevel;
+    private final File workingDir;
+
+    public RhinoCoffeeScriptCompiler(RhinoWorkerHandleFactory rhinoWorkerHandleFactory, Iterable<File> rhinoClasspath, LogLevel logLevel, File workingDir) {
+        this.rhinoWorkerHandleFactory = rhinoWorkerHandleFactory;
+        this.rhinoClasspath = rhinoClasspath;
+        this.logLevel = logLevel;
+        this.workingDir = workingDir;
+    }
+
+    public WorkResult compile(CoffeeScriptCompileSpec spec) {
+        RhinoWorkerHandle<Boolean, SerializableCoffeeScriptCompileSpec> handle = rhinoWorkerHandleFactory.create(rhinoClasspath, createWorkerSpec(), logLevel, new Action<JavaExecSpec>() {
+            public void execute(JavaExecSpec javaExecSpec) {
+                javaExecSpec.setWorkingDir(workingDir);
+            }
+        });
+
+        final Boolean result = handle.process(new SerializableCoffeeScriptCompileSpec(spec));
+
+        return new WorkResult() {
+            public boolean getDidWork() {
+                return result;
+            }
+        };
+    }
+
+    private RhinoWorkerSpec<Boolean, SerializableCoffeeScriptCompileSpec> createWorkerSpec() {
+        return new RhinoWorkerSpec<Boolean, SerializableCoffeeScriptCompileSpec>(
+                Boolean.class, SerializableCoffeeScriptCompileSpec.class, CoffeeScriptCompilerWorker.class
+        );
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsExtension.java
new file mode 100644
index 0000000..c8b43b1
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsExtension.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs;
+
+import org.gradle.api.file.FileCollection;
+
+public class EnvJsExtension {
+
+    public static final String NAME = "envJs";
+
+    public static final String DEFAULT_DEPENDENCY_VERSION = "1.2";
+    public static final String DEFAULT_DEPENDENCY_GROUP = "com.envjs";
+    public static final String DEFAULT_DEPENDENCY_MODULE = "envjs.rhino";
+
+    public static final String CONFIGURATION_NAME = "envJsPlugin";
+
+    private String version;
+    private FileCollection js;
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public FileCollection getJs() {
+        return js;
+    }
+
+    public void setJs(FileCollection js) {
+        this.js = js;
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsPlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsPlugin.groovy
new file mode 100644
index 0000000..d187395
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/EnvJsPlugin.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs
+
+import org.gradle.api.Action
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ResolvableDependencies
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.internal.Factory
+import org.gradle.plugins.javascript.base.JavaScriptExtension
+import org.gradle.plugins.javascript.envjs.browser.BrowserEvaluate
+import org.gradle.plugins.javascript.envjs.internal.EnvJsBrowserEvaluator
+import org.gradle.plugins.javascript.rhino.RhinoExtension
+import org.gradle.plugins.javascript.rhino.RhinoPlugin
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory
+import org.gradle.plugins.javascript.rhino.worker.internal.DefaultRhinoWorkerHandleFactory
+import org.gradle.process.internal.WorkerProcessBuilder
+
+import static org.gradle.plugins.javascript.envjs.EnvJsExtension.*
+import org.gradle.api.logging.LogLevel
+
+class EnvJsPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.plugins.apply(RhinoPlugin)
+        project.plugins.apply(ReportingBasePlugin)
+
+        JavaScriptExtension jsExtension = project.extensions.getByType(JavaScriptExtension)
+        EnvJsExtension envJsExtension = jsExtension.extensions.create(EnvJsExtension.NAME, EnvJsExtension)
+
+        Configuration configuration = addConfiguration(project.configurations, project.dependencies, envJsExtension)
+
+        envJsExtension.conventionMapping.with {
+            map("js") { configuration }
+            map("version") { DEFAULT_DEPENDENCY_VERSION }
+        }
+
+        RhinoExtension rhinoExtension = jsExtension.extensions.getByType(RhinoExtension)
+
+        project.tasks.withType(BrowserEvaluate) { BrowserEvaluate task ->
+            conventionMapping.with {
+                map("evaluator") {
+                    Factory<WorkerProcessBuilder> workerProcessBuilderFactory = project.services.getFactory(WorkerProcessBuilder);
+                    RhinoWorkerHandleFactory handleFactory = new DefaultRhinoWorkerHandleFactory(workerProcessBuilderFactory);
+
+                    LogLevel logLevel = task.logging.level
+                    File workDir = project.projectDir
+                    Factory<File> envJsFactory = new Factory<File>() {
+                        File create() {
+                            envJsExtension.js.singleFile
+                        }
+                    }
+
+                    new EnvJsBrowserEvaluator(handleFactory, rhinoExtension.classpath, envJsFactory, project.gradle.startParameter.logLevel, workDir)
+                }
+            }
+        }
+
+    }
+
+    Configuration addConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, EnvJsExtension extension) {
+        Configuration configuration = configurations.add(EnvJsExtension.CONFIGURATION_NAME)
+        configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
+            void execute(ResolvableDependencies resolvableDependencies) {
+                if (configuration.dependencies.empty) {
+                    String notation = "${DEFAULT_DEPENDENCY_GROUP}:${DEFAULT_DEPENDENCY_MODULE}:${extension.version}@js"
+                    Dependency dependency = dependencies.create(notation)
+                    configuration.dependencies.add(dependency)
+                }
+            }
+        })
+        configuration
+
+
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/browser/BrowserEvaluate.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/browser/BrowserEvaluate.java
new file mode 100644
index 0000000..8d17e47
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/browser/BrowserEvaluate.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.browser;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.tasks.*;
+import org.gradle.plugins.javascript.envjs.http.HttpFileServer;
+import org.gradle.plugins.javascript.envjs.http.simple.SimpleHttpFileServerFactory;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.concurrent.Callable;
+
+public class BrowserEvaluate extends DefaultTask {
+
+    private Object content;
+    private Object resource;
+    private BrowserEvaluator evaluator;
+    private Object result;
+
+    public BrowserEvaluate() {
+        dependsOn(new Callable<TaskDependency>() {
+            public TaskDependency call() throws Exception {
+                return getProject().files(BrowserEvaluate.this.content).getBuildDependencies();
+            }
+        });
+    }
+
+    @InputDirectory
+    public File getContent() {
+        return content == null ? null : getProject().files(content).getSingleFile();
+    }
+
+    public void setContent(Object content) {
+        this.content = content;
+    }
+
+    @Input
+    public String getResource() {
+        return resource.toString();
+    }
+
+    public void setResource(Object resource) {
+        this.resource = resource;
+    }
+
+    //@Input
+    public BrowserEvaluator getEvaluator() {
+        return evaluator;
+    }
+
+    public void setEvaluator(BrowserEvaluator evaluator) {
+        this.evaluator = evaluator;
+    }
+
+    @OutputFile
+    public File getResult() {
+        return result == null ? null : getProject().file(result);
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+
+    @TaskAction
+    void doEvaluate() {
+        HttpFileServer fileServer = new SimpleHttpFileServerFactory().start(getContent(), 0);
+
+        try {
+            Writer resultWriter = new FileWriter(getResult());
+            getEvaluator().evaluate(fileServer.getResourceUrl(getResource()), resultWriter);
+            resultWriter.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            fileServer.stop();
+        }
+
+        setDidWork(true);
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/browser/BrowserEvaluator.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/browser/BrowserEvaluator.java
new file mode 100644
index 0000000..56496cc
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/browser/BrowserEvaluator.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.browser;
+
+import java.io.Writer;
+
+public interface BrowserEvaluator {
+
+    void evaluate(String url, Writer writer);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/HttpFileServer.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/HttpFileServer.java
new file mode 100644
index 0000000..de8533a
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/HttpFileServer.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.http;
+
+import java.io.File;
+
+public interface HttpFileServer {
+
+    int getPort();
+    String getResourceUrl(String path);
+    File getContentRoot();
+    void stop();
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/HttpFileServerFactory.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/HttpFileServerFactory.java
new file mode 100644
index 0000000..d9f9902
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/HttpFileServerFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.http;
+
+import java.io.File;
+
+public interface HttpFileServerFactory {
+
+    HttpFileServer start(File contentRoot, int port);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServer.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServer.java
new file mode 100644
index 0000000..6894e78
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.http.simple;
+
+import org.gradle.internal.Stoppable;
+import org.gradle.plugins.javascript.envjs.http.HttpFileServer;
+
+import java.io.File;
+
+public class SimpleHttpFileServer implements HttpFileServer {
+
+    private final File contentRoot;
+    private final Stoppable stopper;
+    private int port;
+
+    public SimpleHttpFileServer(File contentRoot, int port, Stoppable stopper) {
+        this.contentRoot = contentRoot;
+        this.stopper = stopper;
+        this.port = port;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public String getResourceUrl(String path) {
+        return String.format("http://localhost:%s/%s", port, path.startsWith("/") ? path.substring(1) : path);
+    }
+
+    public File getContentRoot() {
+        return contentRoot;
+    }
+
+    public void stop() {
+        stopper.stop();
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactory.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactory.java
new file mode 100644
index 0000000..58760ce
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactory.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.http.simple;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.internal.Stoppable;
+import org.gradle.plugins.javascript.envjs.http.HttpFileServer;
+import org.gradle.plugins.javascript.envjs.http.HttpFileServerFactory;
+import org.gradle.plugins.javascript.envjs.http.simple.internal.SimpleFileServerContainer;
+import org.simpleframework.http.core.Container;
+import org.simpleframework.http.core.ContainerServer;
+import org.simpleframework.http.resource.FileContext;
+import org.simpleframework.transport.Server;
+import org.simpleframework.transport.connect.Connection;
+import org.simpleframework.transport.connect.SocketConnection;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+public class SimpleHttpFileServerFactory implements HttpFileServerFactory {
+
+    public HttpFileServer start(File contentRoot, int port) {
+        Container container = new SimpleFileServerContainer(new FileContext(contentRoot));
+
+        try {
+            final Server server = new ContainerServer(container);
+            Connection connection = new SocketConnection(server);
+            InetSocketAddress address = new InetSocketAddress(port);
+            InetSocketAddress usedAddress = (InetSocketAddress)connection.connect(address);
+
+            return new SimpleHttpFileServer(contentRoot, usedAddress.getPort(), new Stoppable() {
+                public void stop() {
+                    try {
+                        server.stop();
+                    } catch (IOException e) {
+                        throw new UncheckedIOException(e);
+                    }
+                }
+            });
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/internal/SimpleFileServerContainer.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/internal/SimpleFileServerContainer.java
new file mode 100644
index 0000000..0f3e260
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/http/simple/internal/SimpleFileServerContainer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.http.simple.internal;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.UncheckedIOException;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.core.Container;
+import org.simpleframework.http.resource.Context;
+import org.simpleframework.http.resource.Index;
+
+import java.io.*;
+import java.nio.charset.Charset;
+
+public class SimpleFileServerContainer implements Container {
+
+    private final Context context;
+
+    public SimpleFileServerContainer(Context context) {
+        this.context = context;
+    }
+
+    public void handle(Request req, Response resp) {
+        Index requestIndex = context.getIndex(req.getTarget());
+        File targetFile = requestIndex.getFile();
+
+        if (!targetFile.exists()) {
+            resp.setCode(404);
+            try {
+                resp.getPrintStream().println(String.format("File '%s' does not exist", targetFile.getAbsolutePath()));
+                resp.commit();
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+
+        String contentType = requestIndex.getContentType();
+        resp.set("Content-Type", contentType);
+
+        OutputStream output = null;
+        try {
+            output = resp.getOutputStream();
+
+            if (contentType.startsWith("text/")) {
+                resp.set("Content-Encoding", Charset.defaultCharset().name());
+                Reader input = new FileReader(requestIndex.getFile());
+                IOUtils.copy(input, output);
+                IOUtils.closeQuietly(input);
+            } else {
+                InputStream input = new FileInputStream(requestIndex.getFile());
+                IOUtils.copy(input, output);
+                IOUtils.closeQuietly(input);
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            IOUtils.closeQuietly(output);
+        }
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsBrowserEvaluator.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsBrowserEvaluator.java
new file mode 100644
index 0000000..ee91d6d
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsBrowserEvaluator.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.internal;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.Factory;
+import org.gradle.plugins.javascript.envjs.browser.BrowserEvaluator;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandle;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerSpec;
+import org.gradle.process.JavaExecSpec;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.Writer;
+
+public class EnvJsBrowserEvaluator implements BrowserEvaluator {
+
+    private final RhinoWorkerHandleFactory rhinoWorkerHandleFactory;
+    private final Iterable<File> rhinoClasspath;
+    private final LogLevel logLevel;
+    private final File workingDir;
+    private final Factory<File> envJsFactory;
+
+    public EnvJsBrowserEvaluator(RhinoWorkerHandleFactory rhinoWorkerHandleFactory, Iterable<File> rhinoClasspath, Factory<File> envJsFactory, LogLevel logLevel, File workingDir) {
+        this.rhinoWorkerHandleFactory = rhinoWorkerHandleFactory;
+        this.rhinoClasspath = rhinoClasspath;
+        this.envJsFactory = envJsFactory;
+        this.logLevel = logLevel;
+        this.workingDir = workingDir;
+    }
+
+    public void evaluate(String url, Writer writer) {
+        RhinoWorkerHandle<String, EnvJsEvaluateSpec> handle = rhinoWorkerHandleFactory.create(rhinoClasspath, createWorkerSpec(), logLevel, new Action<JavaExecSpec>() {
+            public void execute(JavaExecSpec javaExecSpec) {
+                javaExecSpec.setWorkingDir(workingDir);
+            }
+        });
+
+        final String result = handle.process(new EnvJsEvaluateSpec(envJsFactory.create(), url));
+
+        try {
+            IOUtils.copy(new StringReader(result), writer);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private RhinoWorkerSpec<String, EnvJsEvaluateSpec> createWorkerSpec() {
+        return new RhinoWorkerSpec<String, EnvJsEvaluateSpec>(
+                String.class, EnvJsEvaluateSpec.class, EnvJsEvaluateWorker.class
+        );
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsEvaluateSpec.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsEvaluateSpec.java
new file mode 100644
index 0000000..41326af
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsEvaluateSpec.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.internal;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class EnvJsEvaluateSpec implements Serializable {
+
+    private final File envJs;
+    private final String url;
+
+    public EnvJsEvaluateSpec(File envJs, String url) {
+        this.envJs = envJs;
+        this.url = url;
+    }
+
+    public File getEnvJs() {
+        return envJs;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsEvaluateWorker.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsEvaluateWorker.java
new file mode 100644
index 0000000..ae52ce0
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/envjs/internal/EnvJsEvaluateWorker.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.internal;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorker;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.RhinoException;
+import org.mozilla.javascript.Scriptable;
+
+import static org.gradle.plugins.javascript.rhino.worker.RhinoWorkerUtils.DefaultScopeOperation;
+import static org.gradle.plugins.javascript.rhino.worker.RhinoWorkerUtils.parseRhino;
+
+public class EnvJsEvaluateWorker implements RhinoWorker<String, EnvJsEvaluateSpec> {
+
+    private static final Logger LOGGER = Logging.getLogger(EnvJsEvaluateWorker.class);
+
+    public String process(EnvJsEvaluateSpec spec) {
+
+        final String targetUrl = spec.getUrl();
+
+        return parseRhino(spec.getEnvJs(), new DefaultScopeOperation<String>() {
+            @Override
+            public void initContext(Context context) {
+                context.setOptimizationLevel(-1);
+            }
+
+            @Override
+            public String action(Scriptable scope, Context context) {
+                scope.put("targetUrl", scope, targetUrl);
+                context.evaluateString(scope, "Envjs({scriptTypes: {'': true, 'text/javascript': true}});", targetUrl, 0, null);
+                Object html = context.evaluateString(scope, "window.location = targetUrl; document.getElementsByTagName('html')[0].innerHTML;", targetUrl, 0, null);
+                return (String) html;
+            }
+        });
+    }
+
+    public Exception convertException(RhinoException rhinoException) {
+        // TODO - need to convert this to a non rhino type in case the version is different back at the client
+        return rhinoException;
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHint.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHint.java
new file mode 100644
index 0000000..091c9e4
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHint.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.jshint;
+
+import com.google.gson.GsonBuilder;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.tasks.*;
+import org.gradle.internal.Factory;
+import org.gradle.plugins.javascript.jshint.internal.JsHintResult;
+import org.gradle.plugins.javascript.jshint.internal.JsHintSpec;
+import org.gradle.plugins.javascript.jshint.internal.JsHintWorker;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandle;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerSpec;
+import org.gradle.plugins.javascript.rhino.worker.internal.DefaultRhinoWorkerHandleFactory;
+import org.gradle.process.JavaExecSpec;
+import org.gradle.process.internal.WorkerProcessBuilder;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URI;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class JsHint extends SourceTask {
+
+    private Object rhinoClasspath;
+    private Object jsHint;
+    private String encoding = "UTF-8";
+    private Object jsonReport;
+
+    @InputFiles
+    public FileCollection getRhinoClasspath() {
+        return getProject().files(rhinoClasspath);
+    }
+
+    public void setRhinoClasspath(Object rhinoClasspath) {
+        this.rhinoClasspath = rhinoClasspath;
+    }
+
+    @InputFiles
+    public FileCollection getJsHint() {
+        return getProject().files(jsHint);
+    }
+
+    public void setJsHint(Object jsHint) {
+        this.jsHint = jsHint;
+    }
+
+    @Input
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+    @OutputFile
+    public File getJsonReport() {
+        return jsonReport == null ? null : getProject().file(jsonReport);
+    }
+
+    public void setJsonReport(Object jsonReport) {
+        this.jsonReport = jsonReport;
+    }
+
+    @TaskAction
+    public void doJsHint() {
+        ProjectInternal projectInternal = (ProjectInternal)getProject();
+        Factory<WorkerProcessBuilder> workerProcessBuilderFactory = projectInternal.getServices().getFactory(WorkerProcessBuilder.class);
+        RhinoWorkerHandleFactory handleFactory = new DefaultRhinoWorkerHandleFactory(workerProcessBuilderFactory);
+
+        LogLevel logLevel = getProject().getGradle().getStartParameter().getLogLevel();
+        RhinoWorkerHandle<JsHintResult, JsHintSpec> rhinoHandle = handleFactory.create(getRhinoClasspath(), createWorkerSpec(), logLevel, new Action<JavaExecSpec>() {
+            public void execute(JavaExecSpec javaExecSpec) {
+                javaExecSpec.setWorkingDir(getProject().getProjectDir());
+            }
+        });
+
+        JsHintSpec spec = new JsHintSpec();
+        spec.setSource(getSource().getFiles()); // flatten because we need to serialize
+        spec.setEncoding(getEncoding());
+        spec.setJsHint(getJsHint().getSingleFile());
+
+        JsHintResult result = rhinoHandle.process(spec);
+        setDidWork(true);
+
+        // TODO - this is all terribly lame. We need some proper reporting here (which means implementing Reporting).
+
+        Logger logger = getLogger();
+        boolean anyErrors = false;
+
+        Map<String, Map<?, ?>> reportData = new LinkedHashMap<String, Map<?, ?>>(result.getResults().size());
+        for (Map.Entry<File, Map<String, Object>> fileEntry: result.getResults().entrySet()) {
+            File file = fileEntry.getKey();
+            Map<String, Object> data = fileEntry.getValue();
+
+            reportData.put(file.getAbsolutePath(), data);
+
+            if (data.containsKey("errors")) {
+                anyErrors = true;
+
+                URI projectDirUri = getProject().getProjectDir().toURI();
+                @SuppressWarnings("unchecked") Map<String, Object> errors = (Map<String, Object>) data.get("errors");
+                if (!errors.isEmpty()) {
+                    URI relativePath = projectDirUri.relativize(file.toURI());
+                    logger.warn("JsHint errors for file: {}", relativePath.getPath());
+                    for (Map.Entry<String, Object> errorEntry : errors.entrySet()) {
+                        @SuppressWarnings("unchecked") Map<String, Object> error = (Map<String, Object>) errorEntry.getValue();
+                        int line = Float.valueOf(error.get("line").toString()).intValue();
+                        int character = Float.valueOf(error.get("character").toString()).intValue();
+                        String reason = error.get("reason").toString();
+
+                        logger.warn("  {}:{} > {}", new Object[] {line, character, reason});
+                    }
+                }
+            }
+        }
+
+        File jsonReportFile = getJsonReport();
+        if (jsonReportFile != null) {
+            try {
+                FileWriter reportWriter = new FileWriter(jsonReportFile);
+                new GsonBuilder().setPrettyPrinting().create().toJson(reportData, reportWriter);
+                reportWriter.close();
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+
+        if (anyErrors) {
+            throw new TaskExecutionException(this, new GradleException("JsHint detected errors"));
+        }
+    }
+
+    private RhinoWorkerSpec<JsHintResult, JsHintSpec> createWorkerSpec() {
+        return new RhinoWorkerSpec<JsHintResult, JsHintSpec>(JsHintResult.class, JsHintSpec.class, JsHintWorker.class);
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintExtension.java
new file mode 100644
index 0000000..5b1e428
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintExtension.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.jshint;
+
+import org.gradle.api.file.FileCollection;
+
+public class JsHintExtension {
+
+    public static final String NAME = "jsHint";
+
+    public static final String DEFAULT_DEPENDENCY_VERSION = "r07";
+    public static final String DEFAULT_DEPENDENCY_GROUP = "com.jshint";
+    public static final String DEFAULT_DEPENDENCY_MODULE = "jshint";
+
+    public static final String CONFIGURATION_NAME = "jsHintPlugin";
+
+    private String version;
+    private FileCollection js;
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public FileCollection getJs() {
+        return js;
+    }
+
+    public void setJs(FileCollection js) {
+        this.js = js;
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintPlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintPlugin.groovy
new file mode 100644
index 0000000..30039b9
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/JsHintPlugin.groovy
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.jshint
+
+import org.gradle.api.Action
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ResolvableDependencies
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.plugins.javascript.base.JavaScriptExtension
+import org.gradle.plugins.javascript.rhino.RhinoExtension
+import org.gradle.plugins.javascript.rhino.RhinoPlugin
+
+import static org.gradle.plugins.javascript.jshint.JsHintExtension.*
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.reporting.ReportingExtension
+
+class JsHintPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.plugins.apply(RhinoPlugin)
+        project.plugins.apply(ReportingBasePlugin)
+
+        JavaScriptExtension jsExtension = project.extensions.getByType(JavaScriptExtension)
+        JsHintExtension jsHintExtension = jsExtension.extensions.create(JsHintExtension.NAME, JsHintExtension)
+        Configuration configuration = addConfiguration(project.configurations, project.dependencies, jsHintExtension)
+
+        jsHintExtension.conventionMapping.with {
+            map("js") { configuration }
+            map("version") { DEFAULT_DEPENDENCY_VERSION }
+        }
+
+        def rhinoExtension = jsExtension.extensions.getByType(RhinoExtension)
+        def reportingExtension = project.extensions.getByType(ReportingExtension)
+
+        project.tasks.withType(JsHint) { JsHint task ->
+            task.conventionMapping.map("rhinoClasspath") { rhinoExtension.classpath }
+            task.conventionMapping.map("jsHint") { jsHintExtension.js }
+            task.conventionMapping.map("jsonReport") { reportingExtension.file("${task.getName()}/report.json") }
+        }
+    }
+
+    Configuration addConfiguration(ConfigurationContainer configurations, DependencyHandler dependencies, JsHintExtension extension) {
+        Configuration configuration = configurations.add(JsHintExtension.CONFIGURATION_NAME)
+        configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
+            void execute(ResolvableDependencies resolvableDependencies) {
+                if (configuration.dependencies.empty) {
+                    String notation = "${DEFAULT_DEPENDENCY_GROUP}:${DEFAULT_DEPENDENCY_MODULE}:${extension.version}@js"
+                    Dependency dependency = dependencies.create(notation)
+                    configuration.dependencies.add(dependency)
+                }
+            }
+        })
+        configuration
+
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintResult.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintResult.java
new file mode 100644
index 0000000..f457156
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintResult.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.jshint.internal;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.Map;
+
+public class JsHintResult implements Serializable {
+
+    private final Map<File, Map<String, Object>> results;
+
+    public JsHintResult(Map<File, Map<String, Object>> results) {
+        this.results = results;
+    }
+
+    public Map<File, Map<String, Object>> getResults() {
+        return results;
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintSpec.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintSpec.java
new file mode 100644
index 0000000..d3b70b3
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintSpec.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.jshint.internal;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class JsHintSpec implements Serializable {
+
+    private File jsHint;
+    private Iterable<File> source;
+    private String encoding;
+
+    public File getJsHint() {
+        return jsHint;
+    }
+
+    public void setJsHint(File jsHint) {
+        this.jsHint = jsHint;
+    }
+
+    public Iterable<File> getSource() {
+        return source;
+    }
+
+    public void setSource(Iterable<File> source) {
+        this.source = source;
+    }
+
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding = encoding;
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintWorker.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintWorker.java
new file mode 100644
index 0000000..23b6ee7
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/jshint/internal/JsHintWorker.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.jshint.internal;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorker;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerUtils;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.RhinoException;
+import org.mozilla.javascript.Scriptable;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.gradle.plugins.javascript.rhino.worker.RhinoWorkerUtils.*;
+
+public class JsHintWorker implements RhinoWorker<JsHintResult, JsHintSpec> {
+
+    private static final Logger LOGGER = Logging.getLogger(JsHintWorker.class);
+
+    public JsHintResult process(JsHintSpec spec) {
+        Scriptable jsHintScope = RhinoWorkerUtils.parse(spec.getJsHint(), "UTF-8");
+
+        String encoding = spec.getEncoding();
+
+        Map<File, Map<String, Object>> results = new LinkedHashMap<File, Map<String, Object>>();
+
+        for (File target : spec.getSource()) {
+            LOGGER.info("Reading file: {}", target.getAbsolutePath());
+            String source = readFile(target, encoding);
+            Map<String, Object> result = jsHint(jsHintScope, source, target.getName());
+            results.put(target, result);
+        }
+
+        return new JsHintResult(results);
+    }
+
+    private Map<String, Object> jsHint(Scriptable jsHintScope, final String source, final String sourceName) {
+        return childScope(jsHintScope, new DefaultScopeOperation<Map<String, Object>>() {
+            public Map<String, Object> action(Scriptable scope, Context context) {
+                scope.put("jsHintSource", scope, source);
+                Object data = context.evaluateString(scope, "JSHINT(jsHintSource); JSHINT.data();", sourceName, 0, null);
+                return toMap((Scriptable) data);
+            }
+        });
+    }
+
+    public Exception convertException(RhinoException rhinoException) {
+        // TODO - need to convert this to a non rhino type in case the version is different back at the client
+        return rhinoException;
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoExtension.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoExtension.java
new file mode 100644
index 0000000..93bd447
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoExtension.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.process.JavaExecSpec;
+
+public class RhinoExtension {
+
+    public static final String NAME = "rhino";
+
+    public static final String RHINO_SHELL_MAIN = "org.mozilla.javascript.tools.shell.Main";
+
+    public static final String DEFAULT_RHINO_DEPENDENCY_VERSION = "1.7R3";
+    public static final String DEFAULT_RHINO_DEPENDENCY_GROUP = "org.mozilla";
+    public static final String DEFAULT_RHINO_DEPENDENCY_MODULE = "rhino";
+
+    public static final String CLASSPATH_CONFIGURATION_NAME = "rhinoPluginRhinoClasspath";
+
+    private FileCollection classpath;
+    private String version;
+
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(FileCollection rhinoClasspath) {
+        this.classpath = rhinoClasspath;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public void configureJavaExec(JavaExecSpec spec) {
+        spec.setMain(RHINO_SHELL_MAIN);
+        spec.setClasspath(getClasspath());
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoPlugin.groovy b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoPlugin.groovy
new file mode 100644
index 0000000..a232a0e
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoPlugin.groovy
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.plugins.javascript.base.JavaScriptExtension
+
+import static org.gradle.plugins.javascript.rhino.RhinoExtension.*
+
+class RhinoPlugin implements Plugin<Project> {
+
+    void apply(Project project) {
+        project.apply(plugin: "javascript-base")
+
+        JavaScriptExtension jsExtension = project.extensions.findByType(JavaScriptExtension)
+        RhinoExtension rhinoExtension = jsExtension.extensions.create(RhinoExtension.NAME, RhinoExtension)
+
+        def configuration = addClasspathConfiguration(project.configurations)
+        configureDefaultRhinoDependency(configuration, project.dependencies, rhinoExtension)
+
+        rhinoExtension.conventionMapping.with {
+            classpath = { configuration }
+            version = { DEFAULT_RHINO_DEPENDENCY_VERSION }
+        }
+
+        project.tasks.withType(RhinoShellExec) { RhinoShellExec task ->
+            task.conventionMapping.with {
+                classpath = { rhinoExtension.classpath }
+                main = { RhinoExtension.RHINO_SHELL_MAIN }
+            }
+            task.classpath = rhinoExtension.classpath
+
+        }
+    }
+
+    private Configuration addClasspathConfiguration(ConfigurationContainer configurations) {
+        def configuration = configurations.create(CLASSPATH_CONFIGURATION_NAME)
+        configuration.visible = false
+        configuration.description = "The default Rhino classpath"
+        configuration
+    }
+
+    void configureDefaultRhinoDependency(Configuration configuration, DependencyHandler dependencyHandler, RhinoExtension extension) {
+        configuration.incoming.beforeResolve {
+            if (configuration.dependencies.empty) {
+                Dependency dependency = dependencyHandler.create("${DEFAULT_RHINO_DEPENDENCY_GROUP}:${DEFAULT_RHINO_DEPENDENCY_MODULE}:${extension.version}")
+                configuration.dependencies.add(dependency)
+            }
+        }
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoShellExec.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoShellExec.java
new file mode 100644
index 0000000..aa62b2f
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/RhinoShellExec.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino;
+
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.JavaExec;
+import org.gradle.api.tasks.Optional;
+import org.gradle.process.JavaExecSpec;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+public class RhinoShellExec extends JavaExec {
+
+    private List<Object> rhinoOptions = new LinkedList<Object>();
+    private List<Object> scriptArgs = new LinkedList<Object>();
+    private Object script;
+
+    public RhinoShellExec() {
+    }
+
+    public List<String> getRhinoOptions() {
+        return CollectionUtils.stringize(rhinoOptions);
+    }
+
+    public void setRhinoOptions(Object... rhinoOptions) {
+        this.rhinoOptions = new LinkedList<Object>(Arrays.asList(rhinoOptions));
+    }
+
+    public void rhinoOptions(Object... rhinoOptions) {
+        this.rhinoOptions.addAll(Arrays.asList(rhinoOptions));
+    }
+
+    public List<String> getScriptArgs() {
+        return CollectionUtils.stringize(scriptArgs);
+    }
+
+    public void setScriptArgs(Object... scriptArgs) {
+        this.scriptArgs = new LinkedList<Object>(Arrays.asList(scriptArgs));
+    }
+
+    public void scriptArgs(Object... scriptArgs) {
+        this.scriptArgs.addAll(Arrays.asList(scriptArgs));
+    }
+
+    @InputFile
+    @Optional
+    public File getScript() {
+        return script == null ? null : getProject().file(script);
+    }
+
+    public void setScript(Object script) {
+        this.script = script;
+    }
+
+    @Override
+    @Input
+    public List<String> getArgs() {
+        List<String> args = new ArrayList<String>(rhinoOptions.size() + 1 + scriptArgs.size());
+        args.addAll(getRhinoOptions());
+        File script = getScript();
+        if (script != null) {
+            args.add(script.getAbsolutePath());
+        }
+        args.addAll(getScriptArgs());
+        return args;
+    }
+
+    @Override
+    public JavaExec setArgs(Iterable<?> applicationArgs) {
+        throw argsUnsupportOperationException();
+    }
+
+    @Override
+    public JavaExec args(Object... args) {
+        throw argsUnsupportOperationException();
+    }
+
+    @Override
+    public JavaExecSpec args(Iterable<?> args) {
+        throw argsUnsupportOperationException();
+    }
+
+    private UnsupportedOperationException argsUnsupportOperationException() {
+        return new UnsupportedOperationException("Cannot set args directly on RhinoExec, use rhinoOptions, scriptArgs and/or script");
+    }
+
+    @Override
+    public void exec() {
+        super.setArgs(getArgs());
+        super.exec();
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorker.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorker.java
new file mode 100644
index 0000000..0a52611
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorker.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker;
+
+import org.mozilla.javascript.RhinoException;
+
+import java.io.Serializable;
+
+public interface RhinoWorker<R extends Serializable, P extends Serializable> {
+
+    R process(P payload);
+
+    Exception convertException(RhinoException rhinoException);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerHandle.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerHandle.java
new file mode 100644
index 0000000..01fa8da
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerHandle.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker;
+
+import java.io.Serializable;
+
+public interface RhinoWorkerHandle<R extends Serializable, P extends Serializable> {
+
+    R process(P payload);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerHandleFactory.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerHandleFactory.java
new file mode 100644
index 0000000..a47e59a
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerHandleFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.process.JavaExecSpec;
+
+import java.io.File;
+import java.io.Serializable;
+
+public interface RhinoWorkerHandleFactory {
+
+    <R extends Serializable, P extends Serializable>
+    RhinoWorkerHandle<R, P> create(Iterable<File> rhinoClasspath, RhinoWorkerSpec<R, P> workerSpec, LogLevel logLevel, Action<JavaExecSpec> javaExecSpecAction);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerSpec.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerSpec.java
new file mode 100644
index 0000000..7dd7e48
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerSpec.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker;
+
+import java.io.Serializable;
+
+public class RhinoWorkerSpec<R extends Serializable, P extends Serializable> implements Serializable {
+
+    private final Class<R> resultType;
+    private final Class<P> payloadType;
+    private final Class<? extends RhinoWorker<R, P>> workerType;
+
+    public RhinoWorkerSpec(Class<R> resultType, Class<P> payloadType, Class<? extends RhinoWorker<R, P>> workerType) {
+        this.resultType = resultType;
+        this.payloadType = payloadType;
+        this.workerType = workerType;
+    }
+
+    public Class<R> getResultType() {
+        return resultType;
+    }
+
+    public Class<P> getPayloadType() {
+        return payloadType;
+    }
+
+    public Class<? extends RhinoWorker<R, P>> getWorkerType() {
+        return workerType;
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerUtils.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerUtils.java
new file mode 100644
index 0000000..28d7c15
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/RhinoWorkerUtils.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker;
+
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.util.GFileUtils;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.FunctionObject;
+import org.mozilla.javascript.Scriptable;
+
+import java.io.*;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public abstract class RhinoWorkerUtils {
+
+    public static interface ScopeOperation<T> {
+        void initContext(Context context);
+        T action(Scriptable scope, Context context);
+    }
+
+    public static class DefaultScopeOperation<T> implements ScopeOperation<T> {
+        public void initContext(Context context) {}
+        public T action(Scriptable scope, Context context) { return null; }
+    }
+
+    public static String readFile(File file, String encoding) {
+        return GFileUtils.readFile(file, encoding);
+    }
+
+    public static void writeFile(String content, File destination, String encoding) {
+        GFileUtils.writeFile(content, destination, encoding);
+    }
+
+    public static Scriptable parse(File source, String encoding) {
+        return parse(source, encoding, null);
+    }
+
+    public static <T> T parseRhino(File rhinoScript, ScopeOperation<T> operation) {
+        Context context = Context.enter();
+        try {
+            operation.initContext(context);
+            Scriptable scope = context.initStandardObjects();
+            String printFunction = "function print(message) {}";
+            context.evaluateString(scope, printFunction, "print", 1, null);
+            context.evaluateString(scope, readFile(rhinoScript, "UTF-8"), rhinoScript.getName(), 1, null);
+            return operation.action(scope, context);
+        } finally {
+            Context.exit();
+        }
+    }
+
+    public static Scriptable parse(File source, String encoding, Action<Context> contextConfig) {
+        Context context = Context.enter();
+        if (contextConfig != null) {
+            contextConfig.execute(context);
+        }
+
+        Scriptable scope = context.initStandardObjects();
+        try {
+            Reader reader = new InputStreamReader(new FileInputStream(source), encoding);
+            try {
+                context.evaluateReader(scope, reader, source.getName(), 0, null);
+            } finally {
+                reader.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            Context.exit();
+        }
+
+        return scope;
+    }
+
+    public static <R> R childScope(Scriptable parentScope, ScopeOperation<R> operation) {
+        Context context = Context.enter();
+        try {
+            operation.initContext(context);
+            Scriptable childScope = context.newObject(parentScope);
+            childScope.setParentScope(parentScope);
+            return operation.action(childScope, context);
+        } finally {
+            Context.exit();
+        }
+    }
+
+    public static Map<String, Object> toMap(Scriptable obj) {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+
+        for (Object id : obj.getIds()) {
+            String key;
+            Object value;
+            if (id instanceof String) {
+                key = (String) id;
+                value = obj.get(key, obj);
+            } else if (id instanceof Integer) {
+                key = id.toString();
+                value = obj.get((Integer) id, obj);
+            } else {
+                throw new IllegalArgumentException(String.format("Unexpected key type: %s (value: %s)", id.getClass().getName(), id));
+            }
+
+            map.put(key, toJavaValue(value));
+        }
+
+        return map;
+    }
+
+    public static Object toJavaValue(Object object) {
+        if (object == null || object.equals(Context.getUndefinedValue())) {
+            return null;
+        } else if (object.getClass().getPackage().getName().startsWith("java.")) {
+            return object;
+        } else if (object instanceof FunctionObject) {
+            throw new IllegalArgumentException(String.format("Cannot convert function object to value (object: %s)", object));
+        } else if (object instanceof Scriptable) {
+            return toMap((Scriptable) object);
+        } else {
+            throw new IllegalArgumentException(String.format("Can't convert JS object %s (type: %s) to native Java object", object, object.getClass().getName()));
+        }
+    }
+
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandle.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandle.java
new file mode 100644
index 0000000..8f82bf6
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandle.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker.internal;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandle;
+import org.gradle.process.internal.WorkerProcess;
+
+import java.io.Serializable;
+import java.util.concurrent.CountDownLatch;
+
+public class DefaultRhinoWorkerHandle<R extends Serializable, P extends Serializable> implements RhinoWorkerHandle<R, P> {
+
+    private final Class<R> resultType;
+    private final WorkerProcess workerProcess;
+
+    public DefaultRhinoWorkerHandle(Class<R> resultType, WorkerProcess workerProcess) {
+        this.resultType = resultType;
+        this.workerProcess = workerProcess;
+    }
+
+    public R process(P payload) {
+        CountDownLatch latch = new CountDownLatch(1);
+        Receiver receiver = new Receiver(latch);
+        workerProcess.start();
+        workerProcess.getConnection().addIncoming(RhinoWorkerClientProtocol.class, receiver);
+        @SuppressWarnings("unchecked") RhinoClientWorkerProtocol<P> worker = workerProcess.getConnection().addOutgoing(RhinoClientWorkerProtocol.class);
+        worker.process(payload);
+
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+        if (receiver.initialisationError != null) {
+            throw UncheckedException.throwAsUncheckedException(receiver.initialisationError);
+        }
+        if (receiver.executionError != null) {
+            throw UncheckedException.throwAsUncheckedException(receiver.executionError);
+        }
+
+        Serializable result = receiver.result;
+        if (result == null) {
+            return null;
+        }
+
+        if (resultType.isInstance(result)) {
+            return resultType.cast(result);
+        } else {
+            throw new IllegalStateException(String.format("Was expecting result of type %s, received %s", resultType, result.getClass()));
+        }
+    }
+
+    private static class Receiver implements RhinoWorkerClientProtocol {
+
+        private final CountDownLatch latch;
+        Exception initialisationError;
+        Serializable result;
+        Exception executionError;
+
+        private Receiver(CountDownLatch latch) {
+            this.latch = latch;
+        }
+
+        public void initialisationError(Exception e) {
+            this.initialisationError = e;
+            latch.countDown();
+        }
+
+        public void receiveResult(Serializable result) {
+            this.result = result;
+            latch.countDown();
+        }
+
+        public void executionError(Exception e) {
+            this.executionError = e;
+            latch.countDown();
+        }
+    }
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandleFactory.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandleFactory.java
new file mode 100644
index 0000000..dc04e0f
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/DefaultRhinoWorkerHandleFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.internal.Factory;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandle;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerHandleFactory;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerSpec;
+import org.gradle.process.JavaExecSpec;
+import org.gradle.process.internal.JavaExecHandleBuilder;
+import org.gradle.process.internal.WorkerProcess;
+import org.gradle.process.internal.WorkerProcessBuilder;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultRhinoWorkerHandleFactory implements RhinoWorkerHandleFactory {
+
+    private final Factory<WorkerProcessBuilder> workerProcessBuilderFactory;
+
+    public DefaultRhinoWorkerHandleFactory(Factory<WorkerProcessBuilder> workerProcessBuilderFactory) {
+        this.workerProcessBuilderFactory = workerProcessBuilderFactory;
+    }
+
+    public <R extends Serializable, P extends Serializable> RhinoWorkerHandle<R, P> create(Iterable<File> rhinoClasspath, RhinoWorkerSpec<R, P> workerSpec, LogLevel logLevel, Action<JavaExecSpec> javaExecSpecAction) {
+        WorkerProcessBuilder builder = workerProcessBuilderFactory.create();
+        builder.setLogLevel(logLevel);
+        builder.applicationClasspath(rhinoClasspath);
+        builder.sharedPackages("org.mozilla.javascript");
+
+        JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
+        if (javaExecSpecAction != null) {
+            javaExecSpecAction.execute(javaCommand);
+        }
+
+        WorkerProcess workerProcess = builder.worker(new RhinoServer<R, P>(workerSpec)).build();
+        return new DefaultRhinoWorkerHandle<R, P>(workerSpec.getResultType(), workerProcess);
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoClientWorkerProtocol.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoClientWorkerProtocol.java
new file mode 100644
index 0000000..340d292
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoClientWorkerProtocol.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker.internal;
+
+import java.io.Serializable;
+
+public interface RhinoClientWorkerProtocol<P extends Serializable> {
+
+    void process(P payload);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoServer.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoServer.java
new file mode 100644
index 0000000..60e49cb
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoServer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker.internal;
+
+import org.gradle.api.Action;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorker;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorkerSpec;
+import org.gradle.process.internal.WorkerProcessContext;
+
+import java.io.Serializable;
+
+public class RhinoServer<R extends Serializable, P extends Serializable> implements Action<WorkerProcessContext>, Serializable {
+
+    private final RhinoWorkerSpec<R, P> workerSpec;
+
+    public RhinoServer(RhinoWorkerSpec<R, P> workerSpec) {
+        this.workerSpec = workerSpec;
+    }
+
+    public void execute(WorkerProcessContext context) {
+        RhinoWorkerClientProtocol clientHandle = context.getServerConnection().addOutgoing(RhinoWorkerClientProtocol.class);
+
+        RhinoWorker<R, P> action;
+
+        try {
+            Class<?> actionClass = getClass().getClassLoader().loadClass(workerSpec.getWorkerType().getName());
+            Object actionObject = actionClass.newInstance();
+            if (actionObject instanceof RhinoWorker) {
+                //noinspection unchecked
+                action = (RhinoWorker<R, P>) actionObject;
+            } else {
+                throw new IllegalStateException(String.format("Implementation class %s is not a transformer", workerSpec.getWorkerType().getName()));
+            }
+
+
+        } catch (Exception e) {
+            clientHandle.initialisationError(e);
+            return;
+        }
+
+        RhinoWorkerReceiver receiver = new RhinoWorkerReceiver<P>(workerSpec.getPayloadType(), clientHandle, action);
+        context.getServerConnection().addIncoming(RhinoClientWorkerProtocol.class, receiver);
+        receiver.waitFor();
+    }
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoWorkerClientProtocol.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoWorkerClientProtocol.java
new file mode 100644
index 0000000..9c7a544
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoWorkerClientProtocol.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker.internal;
+
+import java.io.Serializable;
+
+public interface RhinoWorkerClientProtocol {
+
+    void initialisationError(Exception e);
+
+    void receiveResult(Serializable result);
+
+    void executionError(Exception e);
+
+}
diff --git a/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoWorkerReceiver.java b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoWorkerReceiver.java
new file mode 100644
index 0000000..2454a08
--- /dev/null
+++ b/subprojects/javascript/src/main/groovy/org/gradle/plugins/javascript/rhino/worker/internal/RhinoWorkerReceiver.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino.worker.internal;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.plugins.javascript.rhino.worker.RhinoWorker;
+import org.mozilla.javascript.RhinoException;
+
+import java.io.Serializable;
+import java.util.concurrent.CountDownLatch;
+
+public class RhinoWorkerReceiver<P extends Serializable> implements RhinoClientWorkerProtocol<P> {
+
+    private final Class<P> payloadType;
+    private final RhinoWorker<?, P> worker;
+    private final RhinoWorkerClientProtocol clientHandle;
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+
+    public RhinoWorkerReceiver(Class<P> payloadType, RhinoWorkerClientProtocol clientHandle, RhinoWorker<?, P> worker) {
+        this.payloadType = payloadType;
+        this.clientHandle = clientHandle;
+        this.worker = worker;
+    }
+
+    public void process(P payload) {
+        if (!payloadType.isInstance(payload)) {
+            clientHandle.initialisationError(
+                    new IllegalArgumentException(String.format("Expected payload of type '%s', received '%s' with type '%s'", payloadType.getName(), payload, payload.getClass().getName()))
+            );
+            return;
+        }
+
+        try {
+            Serializable result = worker.process(payload);
+            clientHandle.receiveResult(result);
+        } catch (RhinoException e) {
+            clientHandle.executionError(worker.convertException(e));
+        } catch (Exception e) {
+            clientHandle.executionError(e);
+        } finally {
+            latch.countDown();
+        }
+    }
+
+    public void waitFor() {
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+}
diff --git a/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/coffeescript-base.properties b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/coffeescript-base.properties
new file mode 100644
index 0000000..0097916
--- /dev/null
+++ b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/coffeescript-base.properties
@@ -0,0 +1,2 @@
+implementation-class=org.gradle.plugins.javascript.coffeescript.CoffeeScriptBasePlugin
+
diff --git a/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/envjs.properties b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/envjs.properties
new file mode 100644
index 0000000..04ace7f
--- /dev/null
+++ b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/envjs.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.javascript.envjs.EnvJsPlugin
\ No newline at end of file
diff --git a/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/javascript-base.properties b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/javascript-base.properties
new file mode 100644
index 0000000..adc312b
--- /dev/null
+++ b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/javascript-base.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.javascript.base.JavaScriptBasePlugin
\ No newline at end of file
diff --git a/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/jshint.properties b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/jshint.properties
new file mode 100644
index 0000000..e4df0de
--- /dev/null
+++ b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/jshint.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.javascript.jshint.JsHintPlugin
diff --git a/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/rhino.properties b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/rhino.properties
new file mode 100644
index 0000000..a5b6f1d
--- /dev/null
+++ b/subprojects/javascript/src/main/resources/META-INF/gradle-plugins/rhino.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.plugins.javascript.rhino.RhinoPlugin
\ No newline at end of file
diff --git a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy
new file mode 100644
index 0000000..e2ba0ff
--- /dev/null
+++ b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.base
+
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+
+class JavaScriptBasePluginTest extends Specification {
+
+    Project project = ProjectBuilder.builder().build()
+    JavaScriptExtension extension
+
+    def setup() {
+        apply(plugin: JavaScriptBasePlugin)
+        extension = javaScript
+    }
+
+    def methodMissing(String name, args) {
+        project."$name"(*args)
+    }
+
+    def propertyMissing(String name) {
+        project."$name"
+    }
+
+    def propertyMissing(String name, value) {
+        project."$name" = value
+    }
+
+    def "extension is available"() {
+        expect:
+        extension != null
+    }
+
+    def "can get public repo"() {
+        expect:
+        extension.gradlePublicJavaScriptRepository instanceof MavenArtifactRepository
+        MavenArtifactRepository repo = extension.gradlePublicJavaScriptRepository as MavenArtifactRepository
+        repo.url.toString() == JavaScriptExtension.GRADLE_PUBLIC_JAVASCRIPT_REPO_URL
+    }
+
+}
diff --git a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginTest.groovy b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginTest.groovy
new file mode 100644
index 0000000..e8c90c8
--- /dev/null
+++ b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript
+
+import spock.lang.Specification
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.util.DynamicDelegate
+
+class CoffeeScriptBasePluginTest extends Specification {
+
+    Project project = ProjectBuilder.builder().build()
+    @Delegate DynamicDelegate delegate = new DynamicDelegate(project)
+
+    CoffeeScriptExtension extension
+
+    def setup() {
+        apply plugin: "coffeescript-base"
+        extension = javaScript.coffeeScript
+    }
+
+    def "can get extension"() {
+        expect:
+        extension != null
+    }
+
+}
diff --git a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactoryTest.groovy b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactoryTest.groovy
new file mode 100644
index 0000000..70712d2
--- /dev/null
+++ b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/envjs/http/simple/SimpleHttpFileServerFactoryTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.envjs.http.simple
+
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+import java.nio.charset.Charset
+import org.gradle.util.TestFile
+
+class SimpleHttpFileServerFactoryTest extends Specification {
+
+    @Rule TemporaryFolder tmp = new TemporaryFolder()
+    TestFile root
+
+    def setup() {
+        root = tmp.createDir("content")
+    }
+
+    def "can serve content"() {
+        given:
+
+        root.file("index.html") << "Some content here"
+
+        when:
+        def factory = new SimpleHttpFileServerFactory()
+        def server = factory.start(root, 0)
+
+        then:
+        server.port != 0
+
+        when:
+        HttpURLConnection resource = new URL(server.getResourceUrl("index.html")).openConnection() as HttpURLConnection
+
+        then:
+        resource.getHeaderField("Content-Type") == "text/html"
+        resource.getResponseCode() == 200
+        resource.getHeaderField("Content-Encoding") == Charset.defaultCharset().name()
+        resource.content.text == "Some content here"
+
+        cleanup:
+        server?.stop()
+    }
+
+    def "serves 404"() {
+        when:
+        def factory = new SimpleHttpFileServerFactory()
+        def server = factory.start(root, 0)
+
+        then:
+        server.port != 0
+
+        when:
+        HttpURLConnection resource = new URL(server.getResourceUrl("index.html")).openConnection() as HttpURLConnection
+        resource.content.text
+
+        then:
+        thrown FileNotFoundException
+
+        cleanup:
+        server?.stop()
+    }
+}
diff --git a/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/rhino/RhinoPluginTest.groovy b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/rhino/RhinoPluginTest.groovy
new file mode 100644
index 0000000..4cd55cf
--- /dev/null
+++ b/subprojects/javascript/src/test/groovy/org/gradle/plugins/javascript/rhino/RhinoPluginTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.rhino
+
+import org.gradle.api.Project
+import org.gradle.testfixtures.ProjectBuilder
+import spock.lang.Specification
+import org.gradle.util.DynamicDelegate
+
+class RhinoPluginTest extends Specification {
+
+    Project project = ProjectBuilder.builder().build()
+    @Delegate DynamicDelegate delegate = new DynamicDelegate(project)
+    RhinoExtension extension
+
+    def setup() {
+        apply(plugin: RhinoPlugin)
+        extension = javaScript.rhino
+    }
+
+    def "extension is available"() {
+        expect:
+        extension != null
+    }
+
+    def "default version set"() {
+        expect:
+        extension.version == RhinoExtension.DEFAULT_RHINO_DEPENDENCY_VERSION
+    }
+
+}
diff --git a/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTestFixtures.groovy b/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTestFixtures.groovy
new file mode 100644
index 0000000..bee2e40
--- /dev/null
+++ b/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/base/JavaScriptBasePluginTestFixtures.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.base
+
+class JavaScriptBasePluginTestFixtures {
+
+    static String getGradlePublicJSRepoScript() {
+        """
+        repositories {
+            add ${JavaScriptExtension.NAME}.gradlePublicJavaScriptRepository
+        }
+        """
+    }
+
+    static String getGoogleRepoScript() {
+        """
+        repositories {
+            add ${JavaScriptExtension.NAME}.googleApisRepository
+        }
+        """
+    }
+
+    static void addGradlePublicJsRepoScript(File file) {
+        file << gradlePublicJSRepoScript
+    }
+
+    static void addGoogleRepoScript(File file) {
+        file << googleRepoScript
+    }
+
+}
diff --git a/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginTestFixtures.groovy b/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginTestFixtures.groovy
new file mode 100644
index 0000000..fa349d7
--- /dev/null
+++ b/subprojects/javascript/src/testFixtures/groovy/org/gradle/plugins/javascript/coffeescript/CoffeeScriptBasePluginTestFixtures.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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 org.gradle.plugins.javascript.coffeescript
+
+class CoffeeScriptBasePluginTestFixtures {
+
+    static String getApplyPluginScript() {
+        """
+            apply plugin: "coffeescript-base"
+        """
+    }
+
+    static void addApplyPluginScript(File file) {
+        file << applyPluginScript
+    }
+
+}
diff --git a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
index 378f96d..257ce8a 100644
--- a/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
+++ b/subprojects/jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
@@ -26,9 +26,9 @@ import org.gradle.api.tasks.InputFile;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.Optional;
 import org.gradle.api.tasks.TaskAction;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
-import org.gradle.util.GFileUtils;
 import org.mortbay.jetty.Connector;
 import org.mortbay.jetty.RequestLog;
 import org.mortbay.jetty.Server;
@@ -163,7 +163,7 @@ public abstract class AbstractJettyRunTask extends ConventionTask {
         for (File additionalRuntimeJar : getAdditionalRuntimeJars()) {
             additionalClasspath.add(additionalRuntimeJar);
         }
-        URLClassLoader jettyClassloader = new URLClassLoader(GFileUtils.toURLArray(additionalClasspath), originalClassloader);
+        URLClassLoader jettyClassloader = new URLClassLoader(new DefaultClassPath(additionalClasspath).getAsURLArray(), originalClassloader);
         try {
             Thread.currentThread().setContextClassLoader(jettyClassloader);
             startJetty();
diff --git a/subprojects/launcher/launcher.gradle b/subprojects/launcher/launcher.gradle
index bad4351..1141118 100644
--- a/subprojects/launcher/launcher.gradle
+++ b/subprojects/launcher/launcher.gradle
@@ -1,3 +1,5 @@
+apply from: "$rootDir/gradle/classycle.gradle"
+
 configurations {
     startScriptGenerator
 }
@@ -5,6 +7,7 @@ configurations {
 dependencies {
     groovy libraries.groovy
 
+    compile project(':baseServices')
     compile project(':core')
     compile project(':cli')
     compile project(':ui')
@@ -21,10 +24,14 @@ useTestFixtures()
 jar {
     manifest.mainAttributes('Main-Class': "org.gradle.launcher.GradleMain")
     doFirst {
-        jar.manifest.mainAttributes('Class-Path': "${project(':core').jar.archivePath.name}")
+        jar.manifest.mainAttributes('Class-Path': "${project(':core').jar.archivePath.name} ${project(':baseServices').jar.archivePath.name}")
     }
 }
 
+test {
+    forkEvery = 10
+}
+
 task startScripts(type: StartScriptGenerator) {
     startScriptsDir = new File("$buildDir/startScripts")
     classpath = configurations.startScriptGenerator
@@ -73,4 +80,4 @@ daemonIntegTest {
     //since they are using exclusive daemons they don't contribute to the daemonIntegTest stress/load test.
     //excluding to avoid unnecessary re-running and stealing resources.
     exclude "org/gradle/launcher/daemon/**/*"
-}
\ No newline at end of file
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.groovy
index f01be3a..001bce2 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonConfigurabilityIntegrationSpec.groovy
@@ -17,6 +17,10 @@
 package org.gradle.launcher.daemon
 
 import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.internal.jvm.Jvm
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
 import org.gradle.util.TextUtil
 import spock.lang.IgnoreIf
 
@@ -40,6 +44,27 @@ assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.conta
         """
     }
 
+    @Requires(TestPrecondition.SYMLINKS)
+    def "connects to the daemon if java home is a symlink"() {
+        given:
+        def javaHome = Jvm.current().javaHome
+        def javaLink = distribution.testFile("javaLink")
+        FileSystems.default.createSymbolicLink(javaLink, javaHome)
+
+        String linkPath = TextUtil.escapeString(javaLink.absolutePath)
+        distribution.file("gradle.properties") << "org.gradle.java.home=$linkPath"
+
+        when:
+        buildSucceeds "println 'Hello!'"
+
+        then:
+        javaLink != javaHome
+        javaLink.canonicalFile == javaHome.canonicalFile
+
+        cleanup:
+        javaLink.usingNativeTools().deleteDir()
+    }
+
     //TODO SF add coverage for reconnecting to those daemons.
     def "honours jvm sys property that contain a space in gradle.properties"() {
         given:
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy
new file mode 100644
index 0000000..76086ed
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/SingleUseDaemonIntegrationTest.groovy
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.util.TextUtil
+import spock.lang.IgnoreIf
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+
+ at IgnoreIf({ GradleDistributionExecuter.systemPropertyExecuter == GradleDistributionExecuter.Executer.daemon })
+class SingleUseDaemonIntegrationTest extends AbstractIntegrationSpec {
+    def setup() {
+        // Need forking executer
+        // '-ea' is always set on the forked process. So I've added it explicitly here. // TODO:DAZ Clean this up
+        executer.withForkingExecuter().withEnvironmentVars(["JAVA_OPTS": "-ea"])
+        distribution.requireIsolatedDaemons()
+    }
+
+    def "stops single use daemon on build complete"() {
+        requireJvmArg('-Xmx32m')
+
+        file('build.gradle') << "println 'hello world'"
+
+        when:
+        succeeds()
+
+        then:
+        wasForked()
+        and:
+        executer.getDaemonRegistry().all.empty
+    }
+
+    def "stops single use daemon when build fails"() {
+        requireJvmArg('-Xmx32m')
+
+        file('build.gradle') << "throw new RuntimeException('bad')"
+
+        when:
+        fails()
+
+        then:
+        wasForked()
+        failureHasCause "bad"
+
+        and:
+        executer.getDaemonRegistry().all.empty
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null})
+    def "does not fork build if java home from gradle properties matches current process"() {
+        def alternateJavaHome = AvailableJavaHomes.bestAlternative
+
+        file('gradle.properties') << "org.gradle.java.home=${TextUtil.escapeString(alternateJavaHome.canonicalPath)}"
+
+        file('build.gradle') << "println 'javaHome=' + org.gradle.internal.jvm.Jvm.current().javaHome.absolutePath"
+
+        when:
+        executer.withJavaHome(alternateJavaHome)
+        succeeds()
+
+        then:
+        !wasForked();
+    }
+
+    def "forks build to run when immutable jvm args set regardless of the environment"() {
+        when:
+        requireJvmArg('-Xmx32m')
+        runWithJvmArg('-Xmx32m')
+
+        and:
+        file('build.gradle') << """
+assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx32m')
+"""
+
+        then:
+        succeeds()
+
+        and:
+        wasForked()
+    }
+
+    def "does not fork build and configures system properties from gradle properties"() {
+        when:
+        requireJvmArg('-Dsome-prop=some-value')
+
+        and:
+        file('build.gradle') << """
+assert System.getProperty('some-prop') == 'some-value'
+"""
+
+        then:
+        succeeds()
+
+        and:
+        !wasForked()
+    }
+
+    private def requireJvmArg(String jvmArg) {
+        file('gradle.properties') << "org.gradle.jvmargs=$jvmArg"
+    }
+
+    private def runWithJvmArg(String jvmArg) {
+        executer.withEnvironmentVars(["JAVA_OPTS": "$jvmArg -ea"])
+    }
+
+    private def wasForked() {
+        result.output.contains('fork a new JVM')
+    }
+}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonSmokeIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonSmokeIntegrationSpec.groovy
index b255653..ffde577 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonSmokeIntegrationSpec.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/StoppingDaemonSmokeIntegrationSpec.groovy
@@ -23,7 +23,9 @@ import org.gradle.launcher.daemon.logging.DaemonMessages
 import org.gradle.tests.fixtures.ConcurrentTestUtil
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
+import spock.lang.Ignore
 import spock.lang.Timeout
+
 import static org.gradle.integtests.fixtures.GradleDistributionExecuter.Executer.daemon
 
 /**
@@ -34,6 +36,8 @@ class StoppingDaemonSmokeIntegrationSpec extends DaemonIntegrationSpec {
     @Rule TemporaryFolder temp = new TemporaryFolder()
 
     @Timeout(300)
+    @Ignore
+    //TODO SF - un-ignore when the problem with daemon not shown in the registry file is fixed.
     def "does not deadlock when multiple stop requests are sent"() {
         given: "multiple daemons started"
         3.times { idx ->
@@ -57,6 +61,15 @@ class StoppingDaemonSmokeIntegrationSpec extends DaemonIntegrationSpec {
 
         cleanup: "just in case"
         stopDaemon("cleanup")
+        printDaemonLogs()
+    }
+
+    void printDaemonLogs() {
+        temp.testDir.listFiles()[0].listFiles().findAll { it.name.endsWith("log") }.each {
+            println "-------- $it.name ----------"
+            println it.text
+            println "----------------------------"
+        }
     }
 
     //using separate dist/executer so that we can run them concurrently
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy
index 231f613..0a7e85f 100644
--- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/testing/DaemonsEventSequence.groovy
@@ -16,8 +16,8 @@
 package org.gradle.launcher.daemon.testing
 
 import org.gradle.launcher.daemon.registry.DaemonRegistry
-import org.gradle.messaging.concurrent.DefaultExecutorFactory
-import org.gradle.messaging.concurrent.StoppableExecutor
+import org.gradle.internal.concurrent.DefaultExecutorFactory
+import org.gradle.internal.concurrent.StoppableExecutor
 import org.gradle.internal.Stoppable
 
 import java.util.concurrent.LinkedBlockingQueue
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/GradleMain.java b/subprojects/launcher/src/main/java/org/gradle/launcher/GradleMain.java
index 5ed8c10..cb42a3a 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/GradleMain.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/GradleMain.java
@@ -1,26 +1,28 @@
-/*
- * Copyright 2007 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 org.gradle.launcher;
-
-/**
- * @author Steven Devijver, Hans Dockter
- */
-public class GradleMain {
-    public static void main(String[] args) throws Exception {
-        new ProcessBootstrap().run("org.gradle.launcher.Main", args);
-    }
+/*
+ * Copyright 2007 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 org.gradle.launcher;
+
+import org.gradle.launcher.bootstrap.ProcessBootstrap;
+
+/**
+ * @author Steven Devijver, Hans Dockter
+ */
+public class GradleMain {
+    public static void main(String[] args) throws Exception {
+        new ProcessBootstrap().run("org.gradle.launcher.Main", args);
+    }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java b/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
index 2967ae1..85b33be 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/Main.java
@@ -16,8 +16,8 @@
 package org.gradle.launcher;
 
 import org.gradle.launcher.cli.CommandLineActionFactory;
-import org.gradle.launcher.exec.EntryPoint;
-import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.launcher.bootstrap.EntryPoint;
+import org.gradle.launcher.bootstrap.ExecutionListener;
 
 import java.util.Arrays;
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java b/subprojects/launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java
deleted file mode 100644
index cb5c70a..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/ProcessBootstrap.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.launcher;
-
-import org.gradle.api.internal.ClassPathRegistry;
-import org.gradle.api.internal.DefaultClassPathProvider;
-import org.gradle.api.internal.DefaultClassPathRegistry;
-import org.gradle.api.internal.classpath.DefaultModuleRegistry;
-import org.gradle.util.ClassLoaderFactory;
-import org.gradle.util.ClassPath;
-import org.gradle.util.DefaultClassLoaderFactory;
-import org.gradle.util.MutableURLClassLoader;
-
-import java.lang.reflect.Method;
-
-public class ProcessBootstrap {
-    public void run(String mainClassName, String[] args) {
-        try {
-            runNoExit(mainClassName, args);
-            System.exit(0);
-        } catch (Throwable throwable) {
-            throwable.printStackTrace();
-            System.exit(1);
-        }
-    }
-
-    private void runNoExit(String mainClassName, String[] args) throws Exception {
-        ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new DefaultClassPathProvider(new DefaultModuleRegistry()));
-        ClassLoaderFactory classLoaderFactory = new DefaultClassLoaderFactory();
-        ClassPath antClasspath = classPathRegistry.getClassPath("ANT");
-        ClassPath runtimeClasspath = classPathRegistry.getClassPath("GRADLE_RUNTIME");
-        ClassLoader antClassLoader = classLoaderFactory.createIsolatedClassLoader(antClasspath);
-        ClassLoader runtimeClassLoader = new MutableURLClassLoader(antClassLoader, runtimeClasspath);
-        Thread.currentThread().setContextClassLoader(runtimeClassLoader);
-        Class<?> mainClass = runtimeClassLoader.loadClass(mainClassName);
-        Method mainMethod = mainClass.getMethod("main", String[].class);
-        mainMethod.invoke(null, new Object[]{args});
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/EntryPoint.java b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/EntryPoint.java
new file mode 100644
index 0000000..a2d6fe2
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/EntryPoint.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 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 org.gradle.launcher.bootstrap;
+
+import org.gradle.BuildExceptionReporter;
+import org.gradle.api.Action;
+import org.gradle.configuration.GradleLauncherMetaData;
+import org.gradle.logging.LoggingConfiguration;
+import org.gradle.logging.internal.StreamingStyledTextOutputFactory;
+
+/**
+ * An entry point is the point at which execution will never return from.
+ * <p>
+ * It's purpose is to consistently apply our completion logic of forcing the JVM
+ * to exit at a certain point instead of waiting for all threads to die, and to provide
+ * some consistent unhandled exception catching.
+ * <p>
+ * Entry points may be nested, as is the case when a foreground daemon is started.
+ * <p>
+ * The createCompleter() and createErrorHandler() are not really intended to be overridden
+ * by subclasses as they define our entry point behaviour, but they are protected to enable
+ * testing as it's difficult to test something that will call System.exit().
+ */
+abstract public class EntryPoint implements Runnable {
+
+    /**
+     * Unless the createCompleter() method is overridden, the JVM will exit before returning from this method.
+     */
+    public void run() {
+        RecordingExecutionListener listener = new RecordingExecutionListener();
+        try {
+            doAction(listener);
+        } catch (Throwable e) {
+            createErrorHandler().execute(e);
+            listener.onFailure(e);
+        }
+
+        Throwable failure = listener.getFailure();
+        ExecutionCompleter completer = createCompleter();
+        if (failure == null) {
+            completer.complete();
+        } else {
+            completer.completeWithFailure(failure);
+        }
+    }
+
+    protected ExecutionCompleter createCompleter() {
+        return new ProcessCompleter();
+    }
+
+    protected Action<Throwable> createErrorHandler() {
+        return new BuildExceptionReporter(new StreamingStyledTextOutputFactory(System.err), new LoggingConfiguration(), new GradleLauncherMetaData());
+    }
+
+    protected abstract void doAction(ExecutionListener listener);
+
+    private static class RecordingExecutionListener implements ExecutionListener {
+        private Throwable failure;
+
+        public void onFailure(Throwable failure) {
+            this.failure = failure;
+        }
+
+        public Throwable getFailure() {
+            return failure;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ExecutionCompleter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ExecutionCompleter.java
new file mode 100644
index 0000000..be350bc
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ExecutionCompleter.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2011 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 org.gradle.launcher.bootstrap;
+
+public interface ExecutionCompleter {
+    void complete();
+    void completeWithFailure(Throwable t);
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ExecutionListener.java b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ExecutionListener.java
new file mode 100644
index 0000000..e57a511
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ExecutionListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2010 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 org.gradle.launcher.bootstrap;
+
+/**
+ * Allows an execution action to provide status information to the execution context.
+ *
+ * <p>Note: if the action does not call {@link #onFailure(Throwable)}, then the execution is assumed to have
+ * succeeded.</p>
+ */
+public interface ExecutionListener {
+    /**
+     * Reports a failure of the execution. Note that it is the caller's responsibility to perform any logging of the
+     * failure.
+     *
+     * @param failure The execution failure. This exception has already been logged.
+     */
+    void onFailure(Throwable failure);
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessBootstrap.java b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessBootstrap.java
new file mode 100644
index 0000000..78b064c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessBootstrap.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 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 org.gradle.launcher.bootstrap;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathProvider;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.util.ClassLoaderFactory;
+import org.gradle.util.DefaultClassLoaderFactory;
+import org.gradle.util.MutableURLClassLoader;
+
+import java.lang.reflect.Method;
+
+public class ProcessBootstrap {
+    public void run(String mainClassName, String[] args) {
+        try {
+            runNoExit(mainClassName, args);
+            System.exit(0);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    private void runNoExit(String mainClassName, String[] args) throws Exception {
+        ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new DefaultClassPathProvider(new DefaultModuleRegistry()));
+        ClassLoaderFactory classLoaderFactory = new DefaultClassLoaderFactory();
+        ClassPath antClasspath = classPathRegistry.getClassPath("ANT");
+        ClassPath runtimeClasspath = classPathRegistry.getClassPath("GRADLE_RUNTIME");
+        ClassLoader antClassLoader = classLoaderFactory.createIsolatedClassLoader(antClasspath);
+        ClassLoader runtimeClassLoader = new MutableURLClassLoader(antClassLoader, runtimeClasspath);
+        Thread.currentThread().setContextClassLoader(runtimeClassLoader);
+        Class<?> mainClass = runtimeClassLoader.loadClass(mainClassName);
+        Method mainMethod = mainClass.getMethod("main", String[].class);
+        mainMethod.invoke(null, new Object[]{args});
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessCompleter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessCompleter.java
new file mode 100644
index 0000000..6b7196b
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/bootstrap/ProcessCompleter.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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 org.gradle.launcher.bootstrap;
+
+public class ProcessCompleter implements ExecutionCompleter {
+    public void complete() {
+        System.exit(0);
+    }
+
+    public void completeWithFailure(Throwable t) {
+        System.exit(1);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ActionAdapter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ActionAdapter.java
index 8d8e5be..c195a99 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ActionAdapter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ActionAdapter.java
@@ -17,7 +17,7 @@
 package org.gradle.launcher.cli;
 
 import org.gradle.api.Action;
-import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.launcher.bootstrap.ExecutionListener;
 
 class ActionAdapter implements Action<ExecutionListener> {
     private final Runnable action;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java
index 4aa4622..7c3a7a4 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/BuildActionsFactory.java
@@ -22,8 +22,8 @@ import org.gradle.cli.CommandLineConverter;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
 import org.gradle.configuration.GradleLauncherMetaData;
-import org.gradle.initialization.DefaultBuildRequestMetaData;
 import org.gradle.initialization.DefaultCommandLineConverter;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.launcher.daemon.bootstrap.ForegroundDaemonMain;
 import org.gradle.launcher.daemon.client.DaemonClient;
@@ -33,7 +33,10 @@ import org.gradle.launcher.daemon.client.StopDaemonClientServices;
 import org.gradle.launcher.daemon.configuration.CurrentProcess;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.configuration.ForegroundDaemonConfiguration;
-import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.bootstrap.ExecutionListener;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
+import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter;
 
 import java.io.File;
 import java.lang.management.ManagementFactory;
@@ -82,7 +85,7 @@ class BuildActionsFactory implements CommandLineAction {
             return runBuildWithDaemon(startParameter, daemonParameters, loggingServices);
         }
         if (canUseCurrentProcess(daemonParameters)) {
-            return runBuildInProcess(loggingServices, startParameter);
+            return runBuildInProcess(startParameter, daemonParameters, loggingServices);
         }
         return runBuildInSingleUseDaemon(startParameter, daemonParameters, loggingServices);
     }
@@ -121,8 +124,9 @@ class BuildActionsFactory implements CommandLineAction {
         return currentProcess.configureForBuild(requiredBuildParameters);
     }
 
-    private Action<ExecutionListener> runBuildInProcess(ServiceRegistry loggingServices, StartParameter startParameter) {
-        return new RunBuildAction(startParameter, loggingServices, new DefaultBuildRequestMetaData(clientMetaData(), getBuildStartTime()));
+    private Action<ExecutionListener> runBuildInProcess(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
+        InProcessGradleLauncherActionExecuter executer = new InProcessGradleLauncherActionExecuter(new DefaultGradleLauncherFactory(loggingServices));
+        return daemonBuildAction(startParameter, daemonParameters, executer);
     }
 
     private Action<ExecutionListener> runBuildInSingleUseDaemon(StartParameter startParameter, DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
@@ -141,9 +145,9 @@ class BuildActionsFactory implements CommandLineAction {
         return daemonBuildAction(startParameter, daemonParameters, client);
     }
 
-    private Action<ExecutionListener> daemonBuildAction(StartParameter startParameter, DaemonParameters daemonParameters, DaemonClient client) {
+    private Action<ExecutionListener> daemonBuildAction(StartParameter startParameter, DaemonParameters daemonParameters, GradleLauncherActionExecuter<BuildActionParameters> executer) {
         return new ActionAdapter(
-                new DaemonBuildAction(client, startParameter, getWorkingDir(), clientMetaData(), getBuildStartTime(), daemonParameters.getEffectiveSystemProperties(), System.getenv()));
+                new RunBuildAction(executer, startParameter, getWorkingDir(), clientMetaData(), getBuildStartTime(), daemonParameters.getEffectiveSystemProperties(), System.getenv()));
     }
 
     private long getBuildStartTime() {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java
index 803d1d9..aa77808 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineAction.java
@@ -19,7 +19,7 @@ package org.gradle.launcher.cli;
 import org.gradle.api.Action;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
-import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.launcher.bootstrap.ExecutionListener;
 
 public interface CommandLineAction {
     /**
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java
index ee13a06..b0d37df 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/CommandLineActionFactory.java
@@ -23,8 +23,7 @@ import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
 import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.launcher.exec.ExceptionReportingAction;
-import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.launcher.bootstrap.ExecutionListener;
 import org.gradle.logging.LoggingConfiguration;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/DaemonBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/DaemonBuildAction.java
deleted file mode 100644
index 769c662..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/DaemonBuildAction.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.launcher.cli;
-
-import org.gradle.StartParameter;
-import org.gradle.initialization.BuildClientMetaData;
-import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.DefaultBuildActionParameters;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
-import org.gradle.util.GUtil;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-public class DaemonBuildAction implements Runnable {
-    private final GradleLauncherActionExecuter<BuildActionParameters> executer;
-    private final StartParameter startParameter;
-    private final File currentDir;
-    private final BuildClientMetaData clientMetaData;
-    private final long startTime;
-    private final Map<String, String> systemProperties;
-    private final Map<String, String> envVariables;
-
-    public DaemonBuildAction(GradleLauncherActionExecuter<BuildActionParameters> executer, StartParameter startParameter, File currentDir, BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties, Map<String, String> envVariables) {
-        this.executer = executer;
-        this.startParameter = startParameter;
-        this.currentDir = currentDir;
-        this.clientMetaData = clientMetaData;
-        this.startTime = startTime;
-        this.systemProperties = new HashMap<String, String>();
-        GUtil.addToMap(this.systemProperties, systemProperties);
-        this.envVariables = envVariables;
-    }
-
-    public void run() {
-        executer.execute(new ExecuteBuildAction(startParameter), new DefaultBuildActionParameters(clientMetaData, startTime, systemProperties, envVariables, currentDir));
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExceptionReportingAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExceptionReportingAction.java
new file mode 100644
index 0000000..ba54733
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/ExceptionReportingAction.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 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 org.gradle.launcher.cli;
+
+import org.gradle.api.Action;
+import org.gradle.launcher.bootstrap.ExecutionListener;
+import org.gradle.launcher.exec.ReportedException;
+
+public class ExceptionReportingAction implements Action<ExecutionListener> {
+    private final Action<ExecutionListener> action;
+    private final Action<Throwable> reporter;
+
+    public ExceptionReportingAction(Action<ExecutionListener> action, Action<Throwable> reporter) {
+        this.action = action;
+        this.reporter = reporter;
+    }
+
+    public void execute(ExecutionListener executionListener) {
+        try {
+            action.execute(executionListener);
+        } catch (ReportedException e) {
+            executionListener.onFailure(e.getCause());
+        } catch (Throwable t) {
+            reporter.execute(t);
+            executionListener.onFailure(t);
+        }
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java
index 81b5689..098bd3a 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/GuiActionsFactory.java
@@ -20,7 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.cli.CommandLineParser;
 import org.gradle.cli.ParsedCommandLine;
 import org.gradle.gradleplugin.userinterface.swing.standalone.BlockingApplication;
-import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.launcher.bootstrap.ExecutionListener;
 
 class GuiActionsFactory implements CommandLineAction {
     private static final String GUI = "gui";
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java
index 049507d..548fe47 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/cli/RunBuildAction.java
@@ -15,38 +15,40 @@
  */
 package org.gradle.launcher.cli;
 
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.internal.service.ServiceRegistry;
-import org.gradle.initialization.BuildRequestMetaData;
-import org.gradle.initialization.GradleLauncherFactory;
 import org.gradle.StartParameter;
-import org.gradle.api.Action;
-import org.gradle.initialization.DefaultGradleLauncherFactory;
-import org.gradle.launcher.exec.ExecutionListener;
+import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.DefaultBuildActionParameters;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
+import org.gradle.util.GUtil;
 
-public class RunBuildAction implements Action<ExecutionListener> {
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RunBuildAction implements Runnable {
+    private final GradleLauncherActionExecuter<BuildActionParameters> executer;
     private final StartParameter startParameter;
-    private final ServiceRegistry loggingServices;
-    private final BuildRequestMetaData requestMetaData;
+    private final File currentDir;
+    private final BuildClientMetaData clientMetaData;
+    private final long startTime;
+    private final Map<String, String> systemProperties;
+    private final Map<String, String> envVariables;
 
-    public RunBuildAction(StartParameter startParameter, ServiceRegistry loggingServices, BuildRequestMetaData requestMetaData) {
+    public RunBuildAction(GradleLauncherActionExecuter<BuildActionParameters> executer, StartParameter startParameter, File currentDir, BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties, Map<String, String> envVariables) {
+        this.executer = executer;
         this.startParameter = startParameter;
-        this.loggingServices = loggingServices;
-        this.requestMetaData = requestMetaData;
-    }
-
-    public void execute(ExecutionListener executionListener) {
-        GradleLauncherFactory gradleLauncherFactory = createGradleLauncherFactory(loggingServices);
-        GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameter, requestMetaData);
-        BuildResult buildResult = gradleLauncher.run();
-        Throwable failure = buildResult.getFailure();
-        if (failure != null) {
-            executionListener.onFailure(failure);
-        }
+        this.currentDir = currentDir;
+        this.clientMetaData = clientMetaData;
+        this.startTime = startTime;
+        this.systemProperties = new HashMap<String, String>();
+        GUtil.addToMap(this.systemProperties, systemProperties);
+        this.envVariables = envVariables;
     }
 
-    GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry loggingServices) {
-        return new DefaultGradleLauncherFactory(loggingServices);
+    public void run() {
+        executer.execute(
+                new ExecuteBuildAction(startParameter),
+                new DefaultBuildActionParameters(clientMetaData, startTime, systemProperties, envVariables, currentDir, startParameter.getLogLevel()));
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/DaemonExecHandleBuilder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/DaemonExecHandleBuilder.java
new file mode 100644
index 0000000..0686b98
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/DaemonExecHandleBuilder.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon;
+
+import org.gradle.launcher.daemon.bootstrap.DaemonOutputConsumer;
+import org.gradle.process.internal.ExecHandle;
+import org.gradle.process.internal.ExecHandleBuilder;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * by Szczepan Faber, created at: 5/7/12
+ */
+public class DaemonExecHandleBuilder {
+
+    ExecHandleBuilder builder = new ExecHandleBuilder();
+
+    public ExecHandle build(List<String> args, File workingDir, DaemonOutputConsumer outputConsumer) {
+        builder.commandLine(args);
+        builder.setWorkingDir(workingDir);
+        builder.redirectErrorStream();
+        builder.setTimeout(30000);
+        builder.setDaemon(true);
+        builder.setDisplayName("Gradle build daemon");
+        builder.streamsHandler(outputConsumer);
+        return builder.build();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonGreeter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonGreeter.java
new file mode 100644
index 0000000..54bfe35
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonGreeter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.bootstrap;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
+import org.gradle.process.ExecResult;
+
+/**
+ * by Szczepan Faber, created at: 1/19/12
+ */
+public class DaemonGreeter {
+    private final DocumentationRegistry documentationRegistry;
+
+    public DaemonGreeter(DocumentationRegistry documentationRegistry) {
+        this.documentationRegistry = documentationRegistry;
+    }
+
+    public DaemonDiagnostics parseDaemonOutput(String output, ExecResult result) {
+        if (!new DaemonStartupCommunication().containsGreeting(output)) {
+            throw new GradleException(prepareMessage(output, result));
+        }
+        String[] lines = output.split("\n");
+        //TODO SF don't assume it is the last line
+        String lastLine = lines[lines.length-1];
+        return new DaemonStartupCommunication().readDiagnostics(lastLine);
+    }
+
+    private String prepareMessage(String output, ExecResult result) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(DaemonMessages.UNABLE_TO_START_DAEMON);
+        //TODO SF if possible, include the exit value.
+//        if (result.getExitValue()) {
+//            sb.append("\nThe process has exited with value: ");
+//            sb.append(result.getExecResult().getExitValue()).append(".");
+//        } else {
+//            sb.append("\nThe process may still be running.");
+//        }
+        sb.append("\nThis problem might be caused by incorrect configuration of the daemon.");
+        sb.append("\nFor example, an unrecognized jvm option is used.");
+        sb.append("\nPlease refer to the user guide chapter on the daemon at ");
+        sb.append(documentationRegistry.getDocumentationFor("gradle_daemon"));
+        sb.append("\nPlease read below process output to find out more:");
+        sb.append("\n-----------------------\n");
+        sb.append(output);
+        return sb.toString();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java
index 427f7c2..575089e 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonMain.java
@@ -19,6 +19,8 @@ import com.google.common.io.Files;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.launcher.bootstrap.EntryPoint;
+import org.gradle.launcher.bootstrap.ExecutionListener;
 import org.gradle.launcher.daemon.configuration.DaemonServerConfiguration;
 import org.gradle.launcher.daemon.configuration.DefaultDaemonServerConfiguration;
 import org.gradle.launcher.daemon.context.DaemonContext;
@@ -26,8 +28,6 @@ import org.gradle.launcher.daemon.logging.DaemonMessages;
 import org.gradle.launcher.daemon.server.Daemon;
 import org.gradle.launcher.daemon.server.DaemonServices;
 import org.gradle.launcher.daemon.server.DaemonStoppedException;
-import org.gradle.launcher.exec.EntryPoint;
-import org.gradle.launcher.exec.ExecutionListener;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.logging.internal.OutputEventRenderer;
@@ -48,6 +48,8 @@ public class DaemonMain extends EntryPoint {
     private static final Logger LOGGER = Logging.getLogger(DaemonMain.class);
 
     private final DaemonServerConfiguration configuration;
+    private PrintStream originalOut;
+    private PrintStream originalErr;
 
     public static void main(String[] args) {
         //The first argument is not really used but it is very useful in diagnosing, i.e. running 'jps -m'
@@ -104,6 +106,11 @@ public class DaemonMain extends EntryPoint {
         });
 
         Daemon daemon = startDaemon(daemonServices);
+
+        Long pid = daemonContext.getPid();
+        LOGGER.lifecycle(DaemonMessages.PROCESS_STARTED + ((pid == null)? "":" Pid: " + pid + "."));
+        daemonStarted(pid, daemonLog);
+
         try {
             daemon.awaitIdleTimeout(configuration.getIdleTimeout());
             LOGGER.info("Daemon hit idle timeout (" + configuration.getIdleTimeout() + "ms), stopping...");
@@ -114,6 +121,16 @@ public class DaemonMain extends EntryPoint {
         }
     }
 
+    protected void daemonStarted(Long pid, File daemonLog) {
+        //directly printing to the stream to avoid log level filtering.
+        new DaemonStartupCommunication().printDaemonStarted(originalOut, pid, daemonLog);
+        originalOut.close();
+        originalErr.close();
+
+        //TODO - make this work on windows
+        //originalIn.close();
+    }
+
     protected void initialiseLogging(OutputEventRenderer renderer, LoggingManagerInternal loggingManager, File daemonLog) {
         //create log file
         PrintStream result;
@@ -153,9 +170,9 @@ public class DaemonMain extends EntryPoint {
         return daemon;
     }
 
-    private static void redirectOutputsAndInput(OutputStream log) {
-        PrintStream originalOut = System.out;
-        PrintStream originalErr = System.err;
+    private void redirectOutputsAndInput(OutputStream log) {
+        this.originalOut = System.out;
+        this.originalErr = System.err;
         //InputStream originalIn = System.in;
 
         PrintStream printStream = new PrintStream(log, true);
@@ -163,12 +180,5 @@ public class DaemonMain extends EntryPoint {
         System.setOut(printStream);
         System.setErr(printStream);
         System.setIn(new ByteArrayInputStream(new byte[0]));
-
-        originalOut.println(DaemonMessages.PROCESS_STARTED);
-        originalOut.close();
-        originalErr.close();
-
-        //TODO - make this work on windows
-        //originalIn.close();
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumer.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumer.java
new file mode 100644
index 0000000..c1f7b36
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.bootstrap;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.process.internal.streams.StreamsHandler;
+
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Scanner;
+
+/**
+* by Szczepan Faber, created at: 4/28/12
+*/
+public class DaemonOutputConsumer implements StreamsHandler {
+
+    private final static Logger LOGGER = Logging.getLogger(DaemonOutputConsumer.class);
+
+    private StringWriter output;
+    private StoppableExecutor executor;
+    private Runnable streamConsumer;
+    DaemonStartupCommunication startupCommunication = new DaemonStartupCommunication();
+    private String processOutput;
+
+    public void connectStreams(final Process process, String processName) {
+        if (process == null || processName == null) {
+            throw new IllegalArgumentException("Cannot connect streams because provided process or its name is null");
+        }
+        final InputStream inputStream = process.getInputStream();
+        connectStreams(inputStream, processName);
+    }
+
+    public void start() {
+        if (executor == null || streamConsumer == null) {
+            throw new IllegalStateException("Cannot start consuming daemon output because streams have not been connected first.");
+        }
+        LOGGER.debug("Starting consuming the daemon process output.");
+        output = new StringWriter();
+        executor.execute(streamConsumer);
+    }
+
+    void connectStreams(final InputStream inputStream, String processName) {
+        executor = new DefaultExecutorFactory().create("Read output from: " + processName);
+        streamConsumer = new Runnable() {
+            public void run() {
+                Scanner scanner = new Scanner(inputStream);
+                PrintWriter printer = new PrintWriter(output);
+                try {
+                    while (scanner.hasNext()) {
+                        String line = scanner.nextLine();
+                        LOGGER.debug("daemon out: {}", line);
+                        printer.println(line);
+                        if (startupCommunication.containsGreeting(line)) {
+                            break;
+                        }
+                    }
+                } finally {
+                    scanner.close();
+                }
+            }
+        };
+    }
+
+    public String getProcessOutput() {
+        if (processOutput == null) {
+            throw new IllegalStateException("Unable to get process output as consuming has not finished yet.");
+        }
+        return processOutput;
+    }
+
+    public void stop() {
+        if (executor == null || output == null) {
+            throw new IllegalStateException("Unable to stop output consumer. Was it started?.");
+        }
+        executor.stop();
+        processOutput = output.toString();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunication.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunication.java
new file mode 100644
index 0000000..fc76f56
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunication.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.bootstrap;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+import org.gradle.launcher.daemon.logging.DaemonMessages;
+
+import java.io.File;
+import java.io.PrintStream;
+
+/**
+ * by Szczepan Faber, created at: 4/10/12
+ */
+public class DaemonStartupCommunication {
+
+    private static final String DELIM = ";:"; //this very simple delim should be safe for any kind of path.
+    private static final Logger LOGGER = Logging.getLogger(DaemonStartupCommunication.class);
+
+    public void printDaemonStarted(PrintStream target, Long pid, File daemonLog) {
+        target.println(daemonStartedMessage(pid, daemonLog));
+        //ibm vm 1.6 + windows XP gotchas:
+        //we need to print something else to the stream after we print the daemon greeting.
+        //without it, the parent hangs without receiving the message above (flushing does not help).
+        LOGGER.debug("Completed writing the daemon greeting. Closing streams...");
+        //btw. the ibm vm+winXP also has some issues detecting closed streams by the child but we handle this problem differently.
+    }
+
+    String daemonStartedMessage(Long pid, File daemonLog) {
+        return daemonGreeting() + DELIM + pid + DELIM + daemonLog;
+    }
+
+    public DaemonDiagnostics readDiagnostics(String message) {
+        //TODO SF dont assume the message has correct format
+        String[] split = message.split(DELIM);
+        String pidString = split[1];
+        Long pid = pidString.equals("null")? null : Long.valueOf(pidString);
+        File daemonLog = new File(split[2]);
+        return new DaemonDiagnostics(daemonLog, pid);
+    }
+
+    public boolean containsGreeting(String message) {
+        if (message == null) {
+            throw new IllegalArgumentException("Unable to detect the daemon greeting because the input message is null!");
+        }
+        return message.contains(daemonGreeting());
+    }
+
+    private static String daemonGreeting() {
+        return DaemonMessages.ABOUT_TO_CLOSE_STREAMS;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/ForegroundDaemonMain.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/ForegroundDaemonMain.java
index 20958f8..7725d8d 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/ForegroundDaemonMain.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/ForegroundDaemonMain.java
@@ -37,6 +37,11 @@ public class ForegroundDaemonMain extends DaemonMain {
     }
 
     @Override
+    protected void daemonStarted(Long pid, File daemonLog) {
+        //don't do anything
+    }
+
+    @Override
     protected Daemon startDaemon(DaemonServices daemonServices) {
         Daemon daemon = super.startDaemon(daemonServices);
         daemonServices.get(DaemonRegistry.class).markIdle(daemon.getAddress());
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/GradleDaemon.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/GradleDaemon.java
index 81d3993..554ef00 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/GradleDaemon.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/bootstrap/GradleDaemon.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.launcher.daemon.bootstrap;
 
-import org.gradle.launcher.ProcessBootstrap;
+import org.gradle.launcher.bootstrap.ProcessBootstrap;
 
 public class GradleDaemon {
     public static void main(String[] args) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClient.java
index 489568e..7f65171 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClient.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClient.java
@@ -15,12 +15,13 @@
  */
 package org.gradle.launcher.daemon.client;
 
+import org.gradle.api.internal.specs.ExplainingSpec;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.api.specs.Spec;
-import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.initialization.GradleLauncherAction;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.launcher.daemon.logging.DaemonMessages;
@@ -55,21 +56,31 @@ import java.io.InputStream;
  */
 public class DaemonClient implements GradleLauncherActionExecuter<BuildActionParameters> {
     private static final Logger LOGGER = Logging.getLogger(DaemonClient.class);
-    protected final DaemonConnector connector;
-    protected final BuildClientMetaData clientMetaData;
+    private final DaemonConnector connector;
     private final OutputEventListener outputEventListener;
-    private final Spec<DaemonContext> compatibilitySpec;
+    private final ExplainingSpec<DaemonContext> compatibilitySpec;
     private final InputStream buildStandardInput;
+    private final ExecutorFactory executorFactory;
+    private final IdGenerator<?> idGenerator;
 
     //TODO SF - outputEventListener and buildStandardInput are per-build settings
     //so down the road we should refactor the code accordingly and potentially attach them to BuildActionParameters
-    public DaemonClient(DaemonConnector connector, BuildClientMetaData clientMetaData, OutputEventListener outputEventListener,
-                        Spec<DaemonContext> compatibilitySpec, InputStream buildStandardInput) {
+    public DaemonClient(DaemonConnector connector, OutputEventListener outputEventListener, ExplainingSpec<DaemonContext> compatibilitySpec,
+                        InputStream buildStandardInput, ExecutorFactory executorFactory, IdGenerator<?> idGenerator) {
         this.connector = connector;
-        this.clientMetaData = clientMetaData;
         this.outputEventListener = outputEventListener;
         this.compatibilitySpec = compatibilitySpec;
         this.buildStandardInput = buildStandardInput;
+        this.executorFactory = executorFactory;
+        this.idGenerator = idGenerator;
+    }
+
+    protected IdGenerator<?> getIdGenerator() {
+        return idGenerator;
+    }
+
+    protected DaemonConnector getConnector() {
+        return connector;
     }
 
     /**
@@ -85,7 +96,7 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
         LOGGER.lifecycle("Stopping daemon(s).");
         //iterate and stop all daemons
         while (connection != null) {
-            new StopDispatcher().dispatch(clientMetaData, connection.getConnection());
+            new StopDispatcher(idGenerator).dispatch(connection.getConnection());
             LOGGER.lifecycle("Gradle daemon stopped.");
             connection = connector.maybeConnect(compatibilitySpec);
         }
@@ -98,7 +109,7 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
      * @throws org.gradle.launcher.exec.ReportedException On failure, when the failure has already been logged/reported.
      */
     public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters parameters) {
-        Build build = new Build(action, parameters);
+        Build build = new Build(idGenerator.generateId(), action, parameters);
         int saneNumberOfAttempts = 100; //is it sane enough?
         for (int i = 1; i < saneNumberOfAttempts; i++) {
             DaemonConnection daemonConnection = connector.connect(compatibilitySpec);
@@ -140,7 +151,7 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
     }
 
     private Result monitorBuild(Build build, DaemonDiagnostics diagnostics, Connection<Object> connection) {
-        DaemonClientInputForwarder inputForwarder = new DaemonClientInputForwarder(buildStandardInput, build.getClientMetaData(), connection);
+        DaemonClientInputForwarder inputForwarder = new DaemonClientInputForwarder(buildStandardInput, connection, executorFactory, idGenerator);
         try {
             inputForwarder.start();
             int objectsReceived = 0;
@@ -172,17 +183,12 @@ public class DaemonClient implements GradleLauncherActionExecuter<BuildActionPar
         //we can try sending something to the daemon and try out if he is really dead or use jps
         //if he's really dead we should deregister it if it is not already deregistered.
         //if the daemon is not dead we might continue receiving from him (and try to find the bug in messaging infrastructure)
-        int daemonLogLines = 20;
         LOGGER.error("The message received from the daemon indicates that the daemon has disappeared."
-                + "\nDaemon pid: " + diagnostics.getPid()
-                + "\nDaemon log: " + diagnostics.getDaemonLog()
                 + "\nBuild request sent: " + build
-                + "\nAttempting to read last " + daemonLogLines + " lines from the daemon log...");
+                + "\nAttempting to read last messages from the daemon log...");
 
         try {
-            String tail = GFileUtils.tail(diagnostics.getDaemonLog(), daemonLogLines);
-            LOGGER.error("Last " + daemonLogLines + " lines from " + diagnostics.getDaemonLog().getName() + ":"
-                    + "\n----------\n" + tail + "----------\n");
+            LOGGER.error(diagnostics.describe());
         } catch (GFileUtils.TailReadingException e) {
             LOGGER.error("Unable to read from the daemon log file because of: " + e);
             LOGGER.debug("Problem reading the daemon log file.", e);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java
index 2e6c980..bc7079c 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientInputForwarder.java
@@ -15,19 +15,16 @@
  */
 package org.gradle.launcher.daemon.client;
 
+import org.gradle.api.Action;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-
-import org.gradle.api.Action;
-import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.internal.Stoppable;
-import org.gradle.launcher.daemon.protocol.IoCommand;
-import org.gradle.launcher.daemon.protocol.ForwardInput;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.launcher.daemon.protocol.CloseInput;
-import org.gradle.messaging.remote.internal.InputForwarder;
+import org.gradle.launcher.daemon.protocol.ForwardInput;
+import org.gradle.launcher.daemon.protocol.IoCommand;
 import org.gradle.messaging.dispatch.Dispatch;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
 
 import java.io.InputStream;
 import java.util.concurrent.locks.Lock;
@@ -47,22 +44,22 @@ public class DaemonClientInputForwarder implements Stoppable {
     private boolean started;
 
     private final InputStream inputStream;
-    private final BuildClientMetaData clientMetadata;
     private final Dispatch<? super IoCommand> dispatch;
     private final ExecutorFactory executorFactory;
+    private final IdGenerator<?> idGenerator;
     private final int bufferSize;
 
     private InputForwarder forwarder;
 
-    public DaemonClientInputForwarder(InputStream inputStream, BuildClientMetaData clientMetadata, Dispatch<? super IoCommand> dispatch) {
-        this(inputStream, clientMetadata, dispatch, new DefaultExecutorFactory(), DEFAULT_BUFFER_SIZE);
+    public DaemonClientInputForwarder(InputStream inputStream, Dispatch<? super IoCommand> dispatch, ExecutorFactory executorFactory, IdGenerator<?> idGenerator) {
+        this(inputStream, dispatch, executorFactory, idGenerator, DEFAULT_BUFFER_SIZE);
     }
 
-    public DaemonClientInputForwarder(InputStream inputStream, BuildClientMetaData clientMetadata, Dispatch<? super IoCommand> dispatch, ExecutorFactory executorFactory, int bufferSize) {
+    public DaemonClientInputForwarder(InputStream inputStream, Dispatch<? super IoCommand> dispatch, ExecutorFactory executorFactory, IdGenerator<?> idGenerator, int bufferSize) {
         this.inputStream = inputStream;
-        this.clientMetadata = clientMetadata;
         this.dispatch = dispatch;
         this.executorFactory = executorFactory;
+        this.idGenerator = idGenerator;
         this.bufferSize = bufferSize;
     }
 
@@ -78,13 +75,13 @@ public class DaemonClientInputForwarder implements Stoppable {
                     if (LOGGER.isDebugEnabled()) {
                         LOGGER.debug("Forwarding input to daemon: '{}'", input.replace("\n", "\\n"));
                     }                    
-                    dispatch.dispatch(new ForwardInput(clientMetadata, input.getBytes()));
+                    dispatch.dispatch(new ForwardInput(idGenerator.generateId(), input.getBytes()));
                 }
             };
 
             Runnable onFinish = new Runnable() {
                 public void run() {
-                    CloseInput message = new CloseInput(clientMetadata);
+                    CloseInput message = new CloseInput(idGenerator.generateId());
                     LOGGER.debug("Dispatching close input message: {}", message);
                     dispatch.dispatch(message);
                 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java
index fdc60f5..160e6ff 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServices.java
@@ -17,9 +17,9 @@ package org.gradle.launcher.daemon.client;
 
 import org.gradle.api.internal.DocumentationRegistry;
 import org.gradle.internal.service.ServiceRegistry;
+import org.gradle.launcher.daemon.bootstrap.DaemonGreeter;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.context.DaemonContextBuilder;
-import org.gradle.launcher.daemon.logging.DaemonGreeter;
 import org.gradle.launcher.daemon.registry.DaemonDir;
 import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java
index 78eb821..546a8f9 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientServicesSupport.java
@@ -18,8 +18,9 @@ package org.gradle.launcher.daemon.client;
 import org.gradle.api.internal.DocumentationRegistry;
 import org.gradle.api.internal.GradleDistributionLocator;
 import org.gradle.api.internal.classpath.DefaultModuleRegistry;
-import org.gradle.configuration.GradleLauncherMetaData;
-import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.*;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.internal.service.DefaultServiceRegistry;
@@ -32,6 +33,9 @@ import org.gradle.logging.internal.OutputEventListener;
 import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
 import org.gradle.messaging.remote.internal.OutgoingConnector;
 import org.gradle.messaging.remote.internal.inet.TcpOutgoingConnector;
+import org.gradle.internal.id.CompositeIdGenerator;
+import org.gradle.internal.id.LongIdGenerator;
+import org.gradle.internal.id.UUIDGenerator;
 
 import java.io.InputStream;
 
@@ -62,7 +66,13 @@ abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry
 
     protected DaemonClient createDaemonClient() {
         DaemonCompatibilitySpec matchingContextSpec = new DaemonCompatibilitySpec(get(DaemonContext.class));
-        return new DaemonClient(get(DaemonConnector.class), get(BuildClientMetaData.class), get(OutputEventListener.class), matchingContextSpec, buildStandardInput);
+        return new DaemonClient(
+                get(DaemonConnector.class),
+                get(OutputEventListener.class),
+                matchingContextSpec,
+                buildStandardInput,
+                get(ExecutorFactory.class),
+                get(IdGenerator.class));
     }
 
     protected DaemonContext createDaemonContext() {
@@ -80,8 +90,12 @@ abstract public class DaemonClientServicesSupport extends DefaultServiceRegistry
         return getLoggingServices().get(OutputEventListener.class);
     }
 
-    protected BuildClientMetaData createBuildClientMetaData() {
-        return new GradleLauncherMetaData();
+    protected ExecutorFactory createExecuterFactory() {
+        return new DefaultExecutorFactory();
+    }
+
+    protected IdGenerator<?> createIdGenerator() {
+        return new CompositeIdGenerator(new UUIDGenerator().generateId(), new LongIdGenerator());
     }
 
     protected OutgoingConnector<Object> createOutgoingConnector() {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnection.java
index a3dc2bb..790880e 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnection.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnection.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.launcher.daemon.client;
 
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.messaging.remote.internal.Connection;
 
 /**
@@ -25,11 +26,13 @@ public class DaemonConnection {
     private final String uid;
     private final Connection<Object> connection;
     private final String password;
+    private DaemonDiagnostics diagnostics;
 
-    public DaemonConnection(String uid, Connection<Object> connection, String password) {
+    public DaemonConnection(String uid, Connection<Object> connection, String password, DaemonDiagnostics diagnostics) {
         this.uid = uid;
         this.connection = connection;
         this.password = password;
+        this.diagnostics = diagnostics;
     }
 
     public String getUid() {
@@ -43,4 +46,11 @@ public class DaemonConnection {
     public String getPassword() {
         return this.password;
     }
+
+    /**
+     * @return diagnostics. Can be null - it means we don't have process diagnostics.
+     */
+    public DaemonDiagnostics getDaemonDiagnostics() {
+        return diagnostics;
+    }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnector.java
index d847b2f..595dd63 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnector.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonConnector.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.api.specs.Spec;
+import org.gradle.api.internal.specs.ExplainingSpec;
 import org.gradle.launcher.daemon.context.DaemonContext;
 
 /**
@@ -28,15 +28,15 @@ public interface DaemonConnector {
      *
      * @return A connection to a matching daemon, or null if none running.
      */
-    public DaemonConnection maybeConnect(Spec<? super DaemonContext> constraint);
+    public DaemonConnection maybeConnect(ExplainingSpec<DaemonContext> constraint);
 
     /**
      * Connects to a daemon that matches the given constraint, starting one if required.
      *
      * @return A connection to a matching daemon. Never returns null.
      */
-    public DaemonConnection connect(Spec<? super DaemonContext> constraint);
+    public DaemonConnection connect(ExplainingSpec<DaemonContext> constraint);
 
-    public DaemonConnection createConnection();
+    public DaemonConnection createConnection(ExplainingSpec<DaemonContext> constraint);
 
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonStarter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonStarter.java
index 6fd7b96..222d5a6 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonStarter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonStarter.java
@@ -15,6 +15,8 @@
  */
 package org.gradle.launcher.daemon.client;
 
+import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
+
 public interface DaemonStarter {
-    String startDaemon();
+    DaemonStartupInfo startDaemon();
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
index b06e17a..33dcac4 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonConnector.java
@@ -16,11 +16,13 @@
 package org.gradle.launcher.daemon.client;
 
 import org.gradle.api.GradleException;
+import org.gradle.api.internal.specs.ExplainingSpec;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.api.specs.Spec;
 import org.gradle.internal.UncheckedException;
 import org.gradle.launcher.daemon.context.DaemonContext;
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
 import org.gradle.launcher.daemon.registry.DaemonInfo;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.messaging.remote.internal.ConnectException;
@@ -57,31 +59,30 @@ public class DefaultDaemonConnector implements DaemonConnector {
         return daemonRegistry;
     }
 
-    public DaemonConnection maybeConnect(Spec<? super DaemonContext> constraint) {
+    public DaemonConnection maybeConnect(ExplainingSpec<DaemonContext> constraint) {
         return findConnection(daemonRegistry.getAll(), constraint);
     }
 
-    public DaemonConnection connect(Spec<? super DaemonContext> constraint) {
+    public DaemonConnection connect(ExplainingSpec<DaemonContext> constraint) {
         DaemonConnection connection = findConnection(daemonRegistry.getIdle(), constraint);
         if (connection != null) {
             return connection;
         }
 
-        return createConnection();
+        return createConnection(constraint);
     }
 
-    private DaemonConnection findConnection(List<DaemonInfo> daemonInfos, Spec<? super DaemonContext> constraint) {
+    private DaemonConnection findConnection(List<DaemonInfo> daemonInfos, ExplainingSpec<DaemonContext> constraint) {
         for (DaemonInfo daemonInfo : daemonInfos) {
             if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
                 LOGGER.debug("Found daemon (address: {}, idle: {}) however it's context does not match the desired criteria.\n"
-                        + "  Wanted: {}.\n"
-                        + "  Found:  {}.\n"
-                        + "  Looking for a different daemon...", daemonInfo.getAddress(), daemonInfo.isIdle(), constraint, daemonInfo.getContext());
+                        + constraint.whyUnsatisfied(daemonInfo.getContext()) + "\n"
+                        + "  Looking for a different daemon...", daemonInfo.getAddress(), daemonInfo.isIdle());
                 continue;
             }
 
             try {
-                return connectToDaemon(daemonInfo);
+                return connectToDaemon(daemonInfo, null);
             } catch (ConnectException e) {
                 //this means the daemon died without removing its address from the registry
                 //we can safely remove this address now
@@ -95,10 +96,10 @@ public class DefaultDaemonConnector implements DaemonConnector {
         return null;
     }
 
-    public DaemonConnection createConnection() {
+    public DaemonConnection createConnection(ExplainingSpec<DaemonContext> constraint) {
         LOGGER.info("Starting Gradle daemon");
-        final String uid = daemonStarter.startDaemon();
-        LOGGER.debug("Started Gradle Daemon with UID = {}", uid);
+        final DaemonStartupInfo startupInfo = daemonStarter.startDaemon();
+        LOGGER.debug("Started Gradle Daemon: {}", startupInfo);
         long expiry = System.currentTimeMillis() + connectTimeout;
         do {
             try {
@@ -106,34 +107,39 @@ public class DefaultDaemonConnector implements DaemonConnector {
             } catch (InterruptedException e) {
                 throw UncheckedException.throwAsUncheckedException(e);
             }
-            DaemonConnection daemonConnection = connectToDaemonWithId(uid);
+            DaemonConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
             if (daemonConnection != null) {
                 return daemonConnection;
             }
         } while (System.currentTimeMillis() < expiry);
 
-        throw new GradleException("Timeout waiting to connect to Gradle daemon.");
+        throw new GradleException("Timeout waiting to connect to Gradle daemon.\n" + startupInfo.describe());
     }
 
-    private DaemonConnection connectToDaemonWithId(String uid) throws ConnectException {
+    private DaemonConnection connectToDaemonWithId(DaemonStartupInfo startupInfo, ExplainingSpec<DaemonContext> constraint) throws ConnectException {
         // Look for 'our' daemon among the busy daemons - a daemon will start in busy state so that nobody else will grab it.
         for (DaemonInfo daemonInfo : daemonRegistry.getBusy()) {
-            if (daemonInfo.getContext().getUid().equals(uid)) {
+            if (daemonInfo.getContext().getUid().equals(startupInfo.getUid())) {
                 try {
-                    // TODO:DAZ We should verify the connection using the original daemon constraint
-                    return connectToDaemon(daemonInfo);
+                    if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
+                        throw new GradleException("The newly created daemon process has a different context than expected."
+                                + "\nIt won't be possible to reconnect to this daemon. Context mismatch: "
+                                + "\n" + constraint.whyUnsatisfied(daemonInfo.getContext()));
+                    }
+                    return connectToDaemon(daemonInfo, startupInfo.getDiagnostics());
                 } catch (ConnectException e) {
                     // this means the daemon died without removing its address from the registry
                     // since we have never successfully connected we assume the daemon is dead and remove this address now
                     daemonRegistry.remove(daemonInfo.getAddress());
-                    throw new GradleException("The forked daemon process died before we could connect");
+                    throw new GradleException("The forked daemon process died before we could connect.\n" + startupInfo.describe());
+                    //TODO SF after the refactorings add some coverage for visibility of the daemon tail.
                 }
             }
         }
         return null;
     }
 
-    private DaemonConnection connectToDaemon(DaemonInfo daemonInfo) {
-        return new DaemonConnection(daemonInfo.getContext().getUid(), connector.connect(daemonInfo.getAddress()), daemonInfo.getPassword());
+    private DaemonConnection connectToDaemon(DaemonInfo daemonInfo, DaemonDiagnostics diagnostics) {
+        return new DaemonConnection(daemonInfo.getContext().getUid(), connector.connect(daemonInfo.getAddress()), daemonInfo.getPassword(), diagnostics);
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
index bd18970..3f2ef08 100755
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DefaultDaemonStarter.java
@@ -19,11 +19,17 @@ import org.gradle.api.GradleException;
 import org.gradle.api.internal.classpath.DefaultModuleRegistry;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.launcher.daemon.DaemonExecHandleBuilder;
+import org.gradle.launcher.daemon.bootstrap.DaemonGreeter;
+import org.gradle.launcher.daemon.bootstrap.DaemonOutputConsumer;
 import org.gradle.launcher.daemon.bootstrap.GradleDaemon;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
-import org.gradle.launcher.daemon.logging.DaemonGreeter;
+import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
+import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
 import org.gradle.launcher.daemon.registry.DaemonDir;
-import org.gradle.process.internal.ProcessParentingInitializer;
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.ExecHandle;
+import org.gradle.util.Clock;
 import org.gradle.util.GUtil;
 import org.gradle.util.GradleVersion;
 
@@ -47,7 +53,7 @@ public class DefaultDaemonStarter implements DaemonStarter {
         this.daemonGreeter = daemonGreeter;
     }
 
-    public String startDaemon() {
+    public DaemonStartupInfo startDaemon() {
         DefaultModuleRegistry registry = new DefaultModuleRegistry();
         Set<File> bootstrapClasspath = new LinkedHashSet<File>();
         bootstrapClasspath.addAll(registry.getModule("gradle-launcher").getImplementationClasspath().getAsFiles());
@@ -80,26 +86,33 @@ public class DefaultDaemonStarter implements DaemonStarter {
         //we need to pass them as *program* arguments to avoid problems with getInputArguments().
         daemonArgs.addAll(daemonOpts);
 
-        startProcess(daemonArgs, daemonDir.getVersionedDir());
+        DaemonDiagnostics diagnostics = startProcess(daemonArgs, daemonDir.getVersionedDir());
 
-        return daemonParameters.getUid();
+        return new DaemonStartupInfo(daemonParameters.getUid(), diagnostics);
     }
 
-    private void startProcess(List<String> args, File workingDir) {
+    private DaemonDiagnostics startProcess(final List<String> args, final File workingDir) {
         LOGGER.info("Starting daemon process: workingDir = {}, daemonArgs: {}", workingDir, args);
+        Clock clock = new Clock();
         try {
             workingDir.mkdirs();
-            ProcessParentingInitializer.intitialize();
-            Process process = new ProcessBuilder(args).redirectErrorStream(true).directory(workingDir).start();
-            daemonGreeter.verifyGreetingReceived(process);
 
-            process.getOutputStream().close();
-            process.getErrorStream().close();
-            process.getInputStream().close();
+            DaemonOutputConsumer outputConsumer = new DaemonOutputConsumer();
+            ExecHandle handle = new DaemonExecHandleBuilder().build(args, workingDir, outputConsumer);
+
+            handle.start();
+            LOGGER.debug("Gradle daemon process is starting. Waiting for the daemon to detach...");
+            ExecResult result = handle.waitForFinish();
+            LOGGER.debug("Gradle daemon process is now detached.");
+
+            return daemonGreeter.parseDaemonOutput(outputConsumer.getProcessOutput(), result);
         } catch (GradleException e) {
             throw e;
         } catch (Exception e) {
             throw new GradleException("Could not start Gradle daemon.", e);
+        } finally {
+            LOGGER.info("An attempt to start the daemon took {}.", clock.getTime());
         }
     }
+
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java
index 19d43f3..b4995c0 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonClientServices.java
@@ -17,6 +17,7 @@ package org.gradle.launcher.daemon.client;
 
 import org.gradle.initialization.DefaultGradleLauncherFactory;
 import org.gradle.internal.Factory;
+import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
@@ -34,8 +35,6 @@ import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.logging.internal.OutputEvent;
 import org.gradle.logging.internal.OutputEventListener;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
 
 import java.io.File;
 import java.util.UUID;
@@ -94,15 +93,11 @@ public class EmbeddedDaemonClientServices extends DaemonClientServicesSupport {
         builder.setDaemonRegistryDir(new DaemonDir(new DaemonParameters().getBaseDir()).getRegistry());
     }
 
-    protected ExecutorFactory createExecutorFactory() {
-        return new DefaultExecutorFactory();
-    }
-
     protected DaemonServerConnector createDaemonServerConnector() {
         return new DaemonTcpServerConnector();
     }
 
     protected DaemonStarter createDaemonStarter() {
-        return new EmbeddedDaemonStarter((EmbeddedDaemonRegistry)get(DaemonRegistry.class), getFactory(Daemon.class));
+        return new EmbeddedDaemonStarter(getFactory(Daemon.class));
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonStarter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonStarter.java
index 2994c1e..1cd44f4 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonStarter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/EmbeddedDaemonStarter.java
@@ -15,23 +15,53 @@
  */
 package org.gradle.launcher.daemon.client;
 
+import org.gradle.internal.CompositeStoppable;
 import org.gradle.internal.Factory;
-import org.gradle.launcher.daemon.registry.EmbeddedDaemonRegistry;
+import org.gradle.internal.Stoppable;
+import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
 import org.gradle.launcher.daemon.server.Daemon;
 
-class EmbeddedDaemonStarter implements DaemonStarter {
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
-    private final EmbeddedDaemonRegistry daemonRegistry;
+class EmbeddedDaemonStarter implements DaemonStarter, Stoppable {
     private final Factory<Daemon> daemonFactory;
+    private final List<Daemon> daemons = new ArrayList<Daemon>();
+    private final Lock daemonsLock = new ReentrantLock();
 
-    public EmbeddedDaemonStarter(EmbeddedDaemonRegistry daemonRegistry, Factory<Daemon> daemonFactory) {
-        this.daemonRegistry = daemonRegistry;
+    public EmbeddedDaemonStarter(Factory<Daemon> daemonFactory) {
         this.daemonFactory = daemonFactory;
     }
 
-    public String startDaemon() {
+    public DaemonStartupInfo startDaemon() {
         Daemon daemon = daemonFactory.create();
-        daemonRegistry.startDaemon(daemon);
-        return daemon.getUid();
+        startDaemon(daemon);
+        return new DaemonStartupInfo(daemon.getUid(), null);
     }
-}
\ No newline at end of file
+
+    public void startDaemon(Daemon daemon) {
+        daemonsLock.lock();
+        try {
+            daemons.add(daemon);
+        } finally {
+            daemonsLock.unlock();
+        }
+
+        daemon.start();
+    }
+
+    public void stop() {
+        List<Daemon> daemonsToStop;
+
+        daemonsLock.lock();
+        try {
+            daemonsToStop = new ArrayList<Daemon>(daemons);
+            daemons.clear();
+        } finally {
+            daemonsLock.unlock();
+        }
+
+        new CompositeStoppable(daemonsToStop).stop();
+    }}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/InputForwarder.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/InputForwarder.java
new file mode 100644
index 0000000..7374005
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/InputForwarder.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 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 org.gradle.launcher.daemon.client;
+
+import org.gradle.api.Action;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.util.DisconnectableInputStream;
+import org.gradle.util.LineBufferingOutputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.AsynchronousCloseException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Asynchronously consumes from an input stream for a time,
+ * forwarding a <strong>line</strong> of input at a time to a specified action.
+ *
+ * Note that calling stop() will NOT close the source input stream.
+ */
+public class InputForwarder implements Stoppable {
+
+    private final InputStream input;
+    private final Action<String> forwardTo;
+    private final Runnable onFinish;
+    private final ExecutorFactory executorFactory;
+    private final int bufferSize;
+    private StoppableExecutor forwardingExecuter;
+    private DisconnectableInputStream disconnectableInput;
+    private LineBufferingOutputStream outputBuffer;
+    private final Lock lifecycleLock = new ReentrantLock();
+    private boolean started;
+    private boolean stopped;
+
+    public InputForwarder(InputStream input, Action<String> forwardTo, Runnable onFinish, ExecutorFactory executerFactory, int bufferSize) {
+        this.input = input;
+        this.forwardTo = forwardTo;
+        this.onFinish = onFinish;
+        this.executorFactory = executerFactory;
+        this.bufferSize = bufferSize;
+    }
+
+    public InputForwarder start() {
+        lifecycleLock.lock();
+        try {
+            if (started) {
+                throw new IllegalStateException("input forwarder has already been started");
+            }
+
+            disconnectableInput = new DisconnectableInputStream(input, bufferSize);
+            outputBuffer = new LineBufferingOutputStream(forwardTo, true, bufferSize);
+
+            forwardingExecuter = executorFactory.create("forward input");
+            forwardingExecuter.execute(new Runnable() {
+                public void run() {
+                    byte[] buffer = new byte[bufferSize];
+                    int readCount;
+                    try {
+                        while (true) {
+                            try {
+                                readCount = disconnectableInput.read(buffer, 0, bufferSize);
+                                if (readCount < 0) {
+                                    break;
+                                }
+                            } catch (AsynchronousCloseException e) {
+                                break;
+                            } catch (IOException e) {
+                                // Unsure what the best thing to do is here, should we forward the error?
+                                throw UncheckedException.throwAsUncheckedException(e);
+                            }
+
+                            try {
+                                outputBuffer.write(buffer, 0, readCount);
+                            } catch (IOException e) {
+                                // this shouldn't happen as outputBuffer will only throw if close has been called
+                                // and we own this object exclusively and will not have done that at this time
+                                throw UncheckedException.throwAsUncheckedException(e);
+                            }
+                        }
+                    } finally {
+                        try {
+                            outputBuffer.close(); // will flush any unterminated lines out synchronously
+                        } catch (IOException e) {
+                            throw UncheckedException.throwAsUncheckedException(e);
+                        }
+                    }
+                    
+                    onFinish.run();
+                }
+            });
+
+            started = true;
+        } finally {
+            lifecycleLock.unlock();
+        }
+        
+        return this;
+    }
+
+    public void stop() {
+        lifecycleLock.lock();
+        try {
+            if (!stopped) {
+                try {
+                    disconnectableInput.close();
+                } catch (IOException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+                
+                forwardingExecuter.stop();
+                stopped = true;
+            }
+        } finally {
+            lifecycleLock.unlock();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java
index 3db92ba..d590ad4 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClient.java
@@ -16,9 +16,12 @@
 
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.api.specs.Spec;
-import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.specs.ExplainingSpec;
+import org.gradle.api.internal.specs.ExplainingSpecs;
 import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.protocol.Build;
 import org.gradle.launcher.daemon.protocol.BuildAndStop;
@@ -32,18 +35,22 @@ import java.io.InputStream;
 
 public class SingleUseDaemonClient extends DaemonClient {
     private static final Logger LOGGER = LoggerFactory.getLogger(SingleUseDaemonClient.class);
+    private final DocumentationRegistry documentationRegistry;
 
-    public SingleUseDaemonClient(DaemonConnector connector, BuildClientMetaData clientMetaData, OutputEventListener outputEventListener, Spec<DaemonContext> compatibilitySpec, InputStream buildStandardInput) {
-        super(connector, clientMetaData, outputEventListener, compatibilitySpec, buildStandardInput);
+    public SingleUseDaemonClient(DaemonConnector connector, OutputEventListener outputEventListener, ExplainingSpec<DaemonContext> compatibilitySpec, InputStream buildStandardInput,
+                                 ExecutorFactory executorFactory, IdGenerator<?> idGenerator, DocumentationRegistry documentationRegistry) {
+        super(connector, outputEventListener, compatibilitySpec, buildStandardInput, executorFactory, idGenerator);
+        this.documentationRegistry = documentationRegistry;
     }
 
     @Override
     public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters parameters) {
         LOGGER.warn("Note: in order to honour the org.gradle.jvmargs and/or org.gradle.java.home values specified for this build, it is necessary to fork a new JVM.");
-        LOGGER.warn("This forked JVM is effectively a single-use daemon process. In order to avoid the slowdown associated with this extra process, you might want to consider running Gradle with --daemon.");
-        Build build = new BuildAndStop(action, parameters);
+        LOGGER.warn("To avoid the slowdown associated with this extra process, you might want to consider running Gradle with the daemon enabled.");
+        LOGGER.warn("Please see the user guide chapter on the daemon at {}.", documentationRegistry.getDocumentationFor("gradle_daemon"));
+        Build build = new BuildAndStop(getIdGenerator().generateId(), action, parameters);
 
-        DaemonConnection daemonConnection = connector.createConnection();
+        DaemonConnection daemonConnection = getConnector().createConnection(ExplainingSpecs.<DaemonContext>satisfyAll());
         Connection<Object> connection = daemonConnection.getConnection();
 
         return (T) executeBuild(build, connection);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClientServices.java
index f55dceb..161963a 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClientServices.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/SingleUseDaemonClientServices.java
@@ -16,9 +16,11 @@
 
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.api.internal.DocumentationRegistry;
+import org.gradle.api.internal.specs.ExplainingSpec;
+import org.gradle.api.internal.specs.ExplainingSpecs;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.context.DaemonContext;
@@ -33,7 +35,14 @@ public class SingleUseDaemonClientServices extends DaemonClientServices {
 
     @Override
     protected DaemonClient createDaemonClient() {
-        Spec<DaemonContext> matchNone = Specs.satisfyNone();
-        return new SingleUseDaemonClient(get(DaemonConnector.class), get(BuildClientMetaData.class), get(OutputEventListener.class), matchNone, getBuildStandardInput());
+        ExplainingSpec<DaemonContext> matchNone = ExplainingSpecs.satisfyNone();
+        return new SingleUseDaemonClient(
+                get(DaemonConnector.class),
+                get(OutputEventListener.class),
+                matchNone,
+                getBuildStandardInput(),
+                get(ExecutorFactory.class),
+                get(IdGenerator.class),
+                get(DocumentationRegistry.class));
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java
index 3aedc43..82284a3 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDaemonClientServices.java
@@ -16,9 +16,10 @@
 
 package org.gradle.launcher.daemon.client;
 
-import org.gradle.api.specs.Spec;
-import org.gradle.api.specs.Specs;
-import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.api.internal.specs.ExplainingSpec;
+import org.gradle.api.internal.specs.ExplainingSpecs;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.context.DaemonContext;
@@ -33,7 +34,13 @@ public class StopDaemonClientServices extends DaemonClientServices {
 
     @Override
     protected DaemonClient createDaemonClient() {
-        Spec<DaemonContext> matchAll = Specs.satisfyAll();
-        return new DaemonClient(get(DaemonConnector.class), get(BuildClientMetaData.class), get(OutputEventListener.class), matchAll, getBuildStandardInput());
+        ExplainingSpec<DaemonContext> matchAll = ExplainingSpecs.satisfyAll();
+        return new DaemonClient(
+                get(DaemonConnector.class),
+                get(OutputEventListener.class),
+                matchAll,
+                getBuildStandardInput(),
+                get(ExecutorFactory.class),
+                get(IdGenerator.class));
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDispatcher.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDispatcher.java
index e5a9766..508910d 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDispatcher.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/StopDispatcher.java
@@ -18,22 +18,26 @@ package org.gradle.launcher.daemon.client;
 
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
-import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.launcher.daemon.protocol.Stop;
 import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.internal.id.IdGenerator;
 
 /**
  * @author: Szczepan Faber, created at: 9/13/11
  */
 public class StopDispatcher {
-
     private static final Logger LOGGER = Logging.getLogger(StopDispatcher.class);
+    private final IdGenerator<?> idGenerator;
+
+    public StopDispatcher(IdGenerator<?> idGenerator) {
+        this.idGenerator = idGenerator;
+    }
 
-    public void dispatch(BuildClientMetaData clientMetaData, Connection<Object> connection) {
+    public void dispatch(Connection<Object> connection) {
         //At the moment if we cannot communicate with the daemon we assume it is stopped and print a message to the user
         try {
             try {
-                connection.dispatch(new Stop(clientMetaData));
+                connection.dispatch(new Stop(idGenerator.generateId()));
             } catch (Exception e) {
                 LOGGER.lifecycle("Unable to send the Stop command to one of the daemons. The daemon has already stopped or crashed.");
                 LOGGER.debug("Unable to send Stop.", e);
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonCompatibilitySpec.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonCompatibilitySpec.java
index 6b78591..41fca77 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonCompatibilitySpec.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/context/DaemonCompatibilitySpec.java
@@ -15,9 +15,11 @@
  */
 package org.gradle.launcher.daemon.context;
 
-import org.gradle.api.specs.Spec;
+import org.gradle.api.internal.specs.ExplainingSpec;
 
-public class DaemonCompatibilitySpec implements Spec<DaemonContext> {
+import static org.gradle.util.GFileUtils.canonicalise;
+
+public class DaemonCompatibilitySpec implements ExplainingSpec<DaemonContext> {
 
     private final DaemonContext desiredContext;
     
@@ -26,11 +28,32 @@ public class DaemonCompatibilitySpec implements Spec<DaemonContext> {
     }
 
     public boolean isSatisfiedBy(DaemonContext potentialContext) {
-        return potentialContext.getJavaHome().equals(desiredContext.getJavaHome())
-                && potentialContext.getDaemonOpts().containsAll(desiredContext.getDaemonOpts())
+        return whyUnsatisfied(potentialContext) == null;
+    }
+
+    public String whyUnsatisfied(DaemonContext context) {
+        if (!javaHomeMatches(context)) {
+            return "Java home is different.\n" + description(context);
+        } else if (!daemonOptsMatch(context)) {
+            return "At least one daemon option is different.\n" + description(context);
+        }
+        return null;
+    }
+
+    private String description(DaemonContext context) {
+        return "Wanted: " + this + "\n"
+                + "Actual: " + context + "\n";
+    }
+
+    private boolean daemonOptsMatch(DaemonContext potentialContext) {
+        return potentialContext.getDaemonOpts().containsAll(desiredContext.getDaemonOpts())
                 && potentialContext.getDaemonOpts().size() == desiredContext.getDaemonOpts().size();
     }
 
+    private boolean javaHomeMatches(DaemonContext potentialContext) {
+        return canonicalise(potentialContext.getJavaHome()).equals(canonicalise(desiredContext.getJavaHome()));
+    }
+
     @Override
     public String toString() {
         return desiredContext.toString();
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonDiagnostics.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonDiagnostics.java
index afa515b..2c6538c 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonDiagnostics.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonDiagnostics.java
@@ -16,6 +16,8 @@
 
 package org.gradle.launcher.daemon.diagnostics;
 
+import org.gradle.util.GFileUtils;
+
 import java.io.File;
 import java.io.Serializable;
 
@@ -28,12 +30,16 @@ public class DaemonDiagnostics implements Serializable {
 
     private final Long pid;
     private final File daemonLog;
+    private final static int TAIL_SIZE = 20;
 
     public DaemonDiagnostics(File daemonLog, Long pid) {
         this.daemonLog = daemonLog;
         this.pid = pid;
     }
 
+    /**
+     * @return pid. Can be null, it means the daemon was not able to identify its pid.
+     */
     public Long getPid() {
         return pid;
     }
@@ -41,4 +47,33 @@ public class DaemonDiagnostics implements Serializable {
     public File getDaemonLog() {
         return daemonLog;
     }
+
+    @Override
+    public String toString() {
+        return "{"
+                + "pid=" + pid
+                + ", daemonLog=" + daemonLog
+                + '}';
+    }
+
+    private String tailDaemonLog() {
+        try {
+            String tail = GFileUtils.tail(getDaemonLog(), TAIL_SIZE);
+            return formatTail(tail);
+        } catch (GFileUtils.TailReadingException e) {
+            return "Unable to read the tail from file: " + getDaemonLog().getAbsolutePath();
+        }
+    }
+
+    private String formatTail(String tail) {
+        return "----- Last  " + TAIL_SIZE + " lines from daemon log file - " + getDaemonLog().getName() + " -----\n"
+            + tail
+            + "----- End of the daemon log -----\n";
+    }
+
+    public String describe() {
+        return "Daemon pid: " + pid + "\n"
+             + "  log file: " + daemonLog + "\n"
+             + tailDaemonLog();
+    }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonStartupInfo.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonStartupInfo.java
new file mode 100644
index 0000000..4dfc94e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/diagnostics/DaemonStartupInfo.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.diagnostics;
+
+/**
+ * by Szczepan Faber, created at 4/6/12
+ */
+public class DaemonStartupInfo {
+
+    private String uid;
+    private DaemonDiagnostics diagnostics;
+
+    public DaemonStartupInfo(String uid, DaemonDiagnostics diagnostics) {
+        this.uid = uid;
+        this.diagnostics = diagnostics;
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    /**
+     * @return the diagnostics. Can be null, this means the existing daemon hasn't yet provided the diagnostics.
+     */
+    public DaemonDiagnostics getDiagnostics() {
+        return diagnostics;
+    }
+
+    @Override
+    public String toString() {
+        return "{"
+                + "uid='" + uid + '\''
+                + ", diagnostics=" + diagnostics
+                + '}';
+    }
+
+    public String describe() {
+        if (diagnostics == null) {
+            return "Daemon uid: " + uid + " without diagnostics.";
+        } else {
+            return "Daemon uid: " + uid + " with diagnostics:\n"
+                    + diagnostics.describe();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonGreeter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonGreeter.java
deleted file mode 100644
index 759beb5..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonGreeter.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.launcher.daemon.logging;
-
-import org.apache.commons.io.IOUtils;
-import org.gradle.api.GradleException;
-import org.gradle.api.internal.DocumentationRegistry;
-
-import java.util.List;
-
-/**
- * by Szczepan Faber, created at: 1/19/12
- */
-public class DaemonGreeter {
-    private final DocumentationRegistry documentationRegistry;
-
-    public DaemonGreeter(DocumentationRegistry documentationRegistry) {
-        this.documentationRegistry = documentationRegistry;
-    }
-
-    public void verifyGreetingReceived(Process process) {
-        List<String> lines;
-        try {
-            lines = IOUtils.readLines(process.getInputStream());
-        } catch (Exception e) {
-            throw new GradleException("Unable to get a greeting message from the daemon process."
-                    + " Most likely the daemon process cannot be started.", e);
-        }
-
-        String lastMessage = lines.get(lines.size() - 1);
-        if (!lastMessage.equals(DaemonMessages.PROCESS_STARTED)) {
-            // consider waiting a bit for the exit value
-            // if exit value not provided warn that the daemon didn't exit
-            int exitValue;
-            try {
-                exitValue = process.exitValue();
-            } catch (IllegalThreadStateException e) {
-                throw new GradleException(
-                    DaemonMessages.UNABLE_TO_START_DAEMON + " However, it appears the process hasn't exited yet."
-                    + "\n" + processOutput(lines));
-            }
-            throw new GradleException(DaemonMessages.UNABLE_TO_START_DAEMON + " The exit value was: " + exitValue + "."
-                    + "\n" + processOutput(lines));
-        }
-    }
-
-    private String processOutput(List<String> lines) {
-        StringBuilder sb = new StringBuilder();
-        sb.append("This problem might be caused by incorrect configuration of the daemon.\n");
-        sb.append("For example, an unrecognized jvm option is used.\n");
-        sb.append("Please refer to the user guide chapter on the daemon at ");
-        sb.append(documentationRegistry.getDocumentationFor("gradle_daemon"));
-        sb.append("\n");
-        sb.append("Please read below process output to find out more:\n");
-        sb.append("-----------------------\n");
-
-        for (String line : lines) {
-            sb.append(line).append("\n");
-        }
-        return sb.toString();
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonMessages.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonMessages.java
index c86ee2a..d066e81 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonMessages.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/logging/DaemonMessages.java
@@ -22,6 +22,7 @@ package org.gradle.launcher.daemon.logging;
 public class DaemonMessages {
     
     public final static String PROCESS_STARTED = "Daemon server started.";
+    public final static String ABOUT_TO_CLOSE_STREAMS = "Daemon started. About to close the streams. Daemon details: ";
     public final static String STARTED_RELAYING_LOGS = "The client will now receive all logging from the daemon (pid: ";
     public final static String UNABLE_TO_START_DAEMON = "Unable to start the daemon process.";
     public final static String STARTED_EXECUTING_COMMAND = "Starting executing command: ";
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java
index a24a4b6..b167706 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Build.java
@@ -15,22 +15,15 @@
  */
 package org.gradle.launcher.daemon.protocol;
 
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.initialization.GradleLauncherFactory;
 import org.gradle.launcher.exec.BuildActionParameters;
-import org.gradle.launcher.exec.InitializationAware;
 
 public class Build extends Command {
     private final GradleLauncherAction<?> action;
     private final BuildActionParameters parameters;
 
-    private transient StartParameter startParameter;
-        
-    public Build(GradleLauncherAction<?> action, BuildActionParameters parameters) {
-        super(parameters.getClientMetaData());
+    public Build(Object identifier, GradleLauncherAction<?> action, BuildActionParameters parameters) {
+        super(identifier);
         this.action = action;
         this.parameters = parameters;
     }
@@ -42,33 +35,6 @@ public class Build extends Command {
     public BuildActionParameters getParameters() {
         return parameters;
     }
-    
-    public StartParameter getStartParameter() {
-        if (startParameter == null) {
-            if (action instanceof InitializationAware) {
-                InitializationAware initializationAware = (InitializationAware) action;
-                startParameter = initializationAware.configureStartParameter();
-            } else {
-                startParameter = new StartParameter();
-            }
-        }
-        
-        return startParameter;
-    }
-    
-    public GradleLauncher createGradleLauncher(GradleLauncherFactory launcherFactory) {
-        return launcherFactory.newInstance(getStartParameter(), parameters.getBuildRequestMetaData());
-    }
-    
-    public Object run(GradleLauncherFactory launcherFactory) {
-        return run(createGradleLauncher(launcherFactory));
-    }
-    
-    public Object run(GradleLauncher launcher) {
-        BuildResult buildResult = action.run(launcher);
-        buildResult.rethrowFailure();
-        return action.getResult();
-    }
 
     @Override
     public String toString() {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java
index 633725c..b428089 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/BuildAndStop.java
@@ -20,7 +20,7 @@ import org.gradle.initialization.GradleLauncherAction;
 import org.gradle.launcher.exec.BuildActionParameters;
 
 public class BuildAndStop extends Build {
-    public BuildAndStop(GradleLauncherAction<?> action, BuildActionParameters parameters) {
-        super(action, parameters);
+    public BuildAndStop(Object identifier, GradleLauncherAction<?> action, BuildActionParameters parameters) {
+        super(identifier, action, parameters);
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CloseInput.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CloseInput.java
index d8bad4f..d606925 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CloseInput.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/CloseInput.java
@@ -15,12 +15,8 @@
  */
 package org.gradle.launcher.daemon.protocol;
 
-import org.gradle.initialization.BuildClientMetaData;
-
 public class CloseInput extends IoCommand {
-
-    public CloseInput(BuildClientMetaData clientMetaData) {
-        super(clientMetaData);
+    public CloseInput(Object identifier) {
+        super(identifier);
     }
-
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Command.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Command.java
index 4e8da51..7088235 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Command.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Command.java
@@ -15,32 +15,19 @@
  */
 package org.gradle.launcher.daemon.protocol;
 
-import org.gradle.initialization.BuildClientMetaData;
-
 import java.io.Serializable;
-import java.util.concurrent.atomic.AtomicInteger;
 
 public class Command implements Serializable {
+    private final Object identifier;
 
-    private static final AtomicInteger SEQUENCER = new AtomicInteger(1);
-
-    private final BuildClientMetaData clientMetaData;
-    private final String identifier;
-
-    public Command(BuildClientMetaData clientMetaData) {
-        this.clientMetaData = clientMetaData;
-        //unique only within the process but this should be enough
-        this.identifier = System.currentTimeMillis() + "-" + SEQUENCER.getAndIncrement();
-    }
-
-    public BuildClientMetaData getClientMetaData() {
-        return clientMetaData;
+    public Command(Object identifier) {
+        this.identifier = identifier;
     }
 
     /**
      * @return an id that is guaranteed to be unique in the same process
      */
-    public String getIdentifier() {
+    public Object getIdentifier() {
         return identifier;
     }
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/ForwardInput.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/ForwardInput.java
index 27bfade..dcf3d59 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/ForwardInput.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/ForwardInput.java
@@ -15,14 +15,11 @@
  */
 package org.gradle.launcher.daemon.protocol;
 
-import org.gradle.initialization.BuildClientMetaData;
-
 public class ForwardInput extends IoCommand {
-    
     private final byte[] bytes;
     
-    public ForwardInput(BuildClientMetaData clientMetaData, byte[] bytes) {
-        super(clientMetaData);
+    public ForwardInput(Object identifier, byte[] bytes) {
+        super(identifier);
         this.bytes = bytes;
     }
     
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/IoCommand.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/IoCommand.java
index b176a4d..9839e8e 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/IoCommand.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/IoCommand.java
@@ -15,12 +15,8 @@
  */
 package org.gradle.launcher.daemon.protocol;
 
-import org.gradle.initialization.BuildClientMetaData;
-
 abstract public class IoCommand extends Command {
-
-    public IoCommand(BuildClientMetaData clientMetaData) {
-        super(clientMetaData);
+    protected IoCommand(Object identifier) {
+        super(identifier);
     }
-
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Stop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Stop.java
index 4282ca8..2675aab 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Stop.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/protocol/Stop.java
@@ -15,10 +15,8 @@
  */
 package org.gradle.launcher.daemon.protocol;
 
-import org.gradle.initialization.BuildClientMetaData;
-
 public class Stop extends Command {
-    public Stop(BuildClientMetaData clientMetaData) {
-        super(clientMetaData);
+    public Stop(Object identifier) {
+        super(identifier);
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistry.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistry.java
index a919511..2fbe9bd 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistry.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/EmbeddedDaemonRegistry.java
@@ -17,9 +17,6 @@ package org.gradle.launcher.daemon.registry;
 
 import org.gradle.api.specs.Spec;
 import org.gradle.api.specs.Specs;
-import org.gradle.internal.CompositeStoppable;
-import org.gradle.internal.Stoppable;
-import org.gradle.launcher.daemon.server.Daemon;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.messaging.remote.Address;
 
@@ -27,8 +24,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * A daemon registry for daemons running in the same JVM.
@@ -40,12 +35,8 @@ import java.util.concurrent.locks.ReentrantLock;
  * by the time they are returned to the caller. Clients must therefore be prepared for this and expect connection failures, either through
  * the endpoint disappearing or becoming busy between asking for idle daemons and trying to connect.
  */
-public class EmbeddedDaemonRegistry implements DaemonRegistry, Stoppable {
-
+public class EmbeddedDaemonRegistry implements DaemonRegistry {
     private final Map<Address, DaemonInfo> daemonInfos = new ConcurrentHashMap<Address, DaemonInfo>();
-    private final List<Daemon> daemons = new ArrayList<Daemon>();
-    private final Lock daemonsLock = new ReentrantLock();
-
     private final Spec<DaemonInfo> allSpec = new Spec<DaemonInfo>() {
         public boolean isSatisfiedBy(DaemonInfo entry) {
             return true;
@@ -108,43 +99,4 @@ public class EmbeddedDaemonRegistry implements DaemonRegistry, Stoppable {
 
         return matches;
     }
-
-    /**
-     * Returns all daemons started in this registry since construction or most recent stopDaemons().
-     * <p>
-     * The returned daemons are not guaranteed to be running as they may have been stopped individually.
-     */
-    public List<Daemon> getDaemons() {
-        daemonsLock.lock();
-        try {
-            return new ArrayList<Daemon>(daemons);
-        } finally {
-            daemonsLock.unlock();
-        }
-    }
-    
-    public void startDaemon(Daemon daemon) {
-        daemonsLock.lock();
-        try {
-            daemons.add(daemon);
-        } finally {
-            daemonsLock.unlock();
-        }
-
-        daemon.start();
-    }
-
-    public void stop() {
-        List<Daemon> daemonsToStop;
-        
-        daemonsLock.lock();
-        try {
-            daemonsToStop = new ArrayList<Daemon>(daemons);
-            daemons.clear();
-        } finally {
-            daemonsLock.unlock();
-        }
-        
-        new CompositeStoppable(daemonsToStop).stop();
-    }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java
index 2139df4..3de9a65 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/PersistentDaemonRegistry.java
@@ -16,8 +16,11 @@
 
 package org.gradle.launcher.daemon.registry;
 
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 import org.gradle.cache.DefaultSerializer;
 import org.gradle.cache.PersistentStateCache;
+import org.gradle.cache.internal.FileIntegrityViolationSuppressingPersistentStateCacheDecorator;
 import org.gradle.cache.internal.FileLockManager;
 import org.gradle.cache.internal.OnDemandFileAccess;
 import org.gradle.cache.internal.SimpleStateCache;
@@ -36,19 +39,23 @@ import java.util.concurrent.locks.ReentrantLock;
  * @author: Szczepan Faber, created at: 8/18/11
  */
 public class PersistentDaemonRegistry implements DaemonRegistry {
-    private final SimpleStateCache<DaemonRegistryContent> cache;
+    private final PersistentStateCache<DaemonRegistryContent> cache;
     private final Lock lock = new ReentrantLock();
     private final File registryFile;
 
+    private static final Logger LOGGER = Logging.getLogger(PersistentDaemonRegistry.class);
+
     public PersistentDaemonRegistry(File registryFile, FileLockManager fileLockManager) {
         this.registryFile = registryFile;
-        cache = new SimpleStateCache<DaemonRegistryContent>(
-                registryFile,
-                new OnDemandFileAccess(
+        cache = new FileIntegrityViolationSuppressingPersistentStateCacheDecorator<DaemonRegistryContent>(
+                new SimpleStateCache<DaemonRegistryContent>(
                         registryFile,
-                        "daemon addresses registry",
-                        fileLockManager),
-                new DefaultSerializer<DaemonRegistryContent>());
+                        new OnDemandFileAccess(
+                                registryFile,
+                                "daemon addresses registry",
+                                fileLockManager),
+                        new DefaultSerializer<DaemonRegistryContent>()
+                ));
     }
 
     public List<DaemonInfo> getAll() {
@@ -99,6 +106,7 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
 
     public void remove(final Address address) {
         lock.lock();
+        LOGGER.debug("Removing daemon address: {}", address);
         try {
             cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
                 public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
@@ -114,6 +122,7 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
 
     public void markBusy(final Address address) {
         lock.lock();
+        LOGGER.debug("Marking busy by address: {}", address);
         try {
             cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
                 public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
@@ -130,6 +139,7 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
 
     public void markIdle(final Address address) {
         lock.lock();
+        LOGGER.debug("Marking idle by address: {}", address);
         try {
             cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
                 public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
@@ -145,6 +155,7 @@ public class PersistentDaemonRegistry implements DaemonRegistry {
 
     public synchronized void store(final Address address, final DaemonContext daemonContext, final String password) {
         lock.lock();
+        LOGGER.debug("Storing daemon address: {}, context: {}", address, daemonContext);
         try {
             cache.update(new PersistentStateCache.UpdateAction<DaemonRegistryContent>() {
                 public DaemonRegistryContent update(DaemonRegistryContent oldValue) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java
index e7b0bf6..9d3d08d 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/Daemon.java
@@ -18,14 +18,14 @@ package org.gradle.launcher.daemon.server;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.logging.DaemonMessages;
 import org.gradle.launcher.daemon.protocol.Command;
 import org.gradle.launcher.daemon.protocol.DaemonFailure;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.launcher.daemon.server.exec.DaemonCommandExecuter;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.internal.Connection;
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java
index 4526c0e..c1a0590 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java
@@ -18,6 +18,8 @@ package org.gradle.launcher.daemon.server;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.internal.service.DefaultServiceRegistry;
@@ -30,8 +32,6 @@ import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
 import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter;
 import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
 
 import java.io.File;
 import java.util.UUID;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java
index d5f86b5..082a93c 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonStateCoordinator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2012 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.
@@ -19,8 +19,9 @@ package org.gradle.launcher.daemon.server;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.Stoppable;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
 import org.gradle.launcher.daemon.server.exec.DaemonCommandExecution;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.launcher.daemon.server.exec.DaemonStateControl;
 
 import java.util.Date;
 import java.util.concurrent.locks.Condition;
@@ -35,7 +36,7 @@ import java.util.concurrent.locks.ReentrantLock;
  *
  * This is not exposed to clients of the daemon.
  */
-public class DaemonStateCoordinator implements Stoppable {
+public class DaemonStateCoordinator implements Stoppable, DaemonStateControl {
 
     private static final org.gradle.api.logging.Logger LOGGER = Logging.getLogger(DaemonStateCoordinator.class);
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
index 04bc8fd..f9d7bfe 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonTcpServerConnector.java
@@ -16,18 +16,14 @@
 package org.gradle.launcher.daemon.server;
 
 import org.gradle.api.Action;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.launcher.daemon.logging.DaemonMessages;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.id.UUIDGenerator;
 import org.gradle.messaging.remote.Address;
 import org.gradle.messaging.remote.ConnectEvent;
 import org.gradle.messaging.remote.internal.Connection;
 import org.gradle.messaging.remote.internal.DefaultMessageSerializer;
-import org.gradle.messaging.remote.internal.SynchronizedDispatch;
 import org.gradle.messaging.remote.internal.inet.InetAddressFactory;
 import org.gradle.messaging.remote.internal.inet.TcpIncomingConnector;
-import org.gradle.util.UUIDGenerator;
 
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
@@ -36,9 +32,6 @@ import java.util.concurrent.locks.ReentrantLock;
  * Opens a TCP connection for clients to connect to to communicate with a daemon.
  */
 public class DaemonTcpServerConnector implements DaemonServerConnector {
-
-    private static final Logger LOGGER = Logging.getLogger(DaemonServerConnector.class);
-
     final private TcpIncomingConnector<Object> incomingConnector;
 
     private boolean started;
@@ -67,11 +60,9 @@ public class DaemonTcpServerConnector implements DaemonServerConnector {
             // Hold the lock until we actually start accepting connections for the case when stop is called from another
             // thread while we are in the middle here.
 
-            LOGGER.lifecycle(DaemonMessages.PROCESS_STARTED);
-
             Action<ConnectEvent<Connection<Object>>> connectEvent = new Action<ConnectEvent<Connection<Object>>>() {
                 public void execute(ConnectEvent<Connection<Object>> connectionConnectEvent) {
-                    handler.handle(new SynchronizedDispatch<Object>(connectionConnectEvent.getConnection()));
+                    handler.handle(new SynchronizedDispatchConnection<Object>(connectionConnectEvent.getConnection()));
                 }
             };
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/SynchronizedDispatchConnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/SynchronizedDispatchConnection.java
new file mode 100644
index 0000000..f43eaf6
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/SynchronizedDispatchConnection.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2009 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 org.gradle.launcher.daemon.server;
+
+import org.gradle.internal.concurrent.Synchronizer;
+import org.gradle.messaging.remote.internal.Connection;
+
+/**
+ * Connection decorator that synchronizes dispatching.
+ * <p>
+ * by Szczepan Faber, created at: 2/27/12
+ */
+public class SynchronizedDispatchConnection<T> implements Connection<T> {
+    
+    private final Synchronizer sync = new Synchronizer();
+    private final Connection<T> delegate;
+
+    public SynchronizedDispatchConnection(Connection<T> delegate) {
+        this.delegate = delegate;
+    }
+    
+    public void requestStop() {
+        delegate.requestStop();
+    }
+
+    public void dispatch(final T message) {
+        sync.synchronize(new Runnable() {
+            public void run() {
+                delegate.dispatch(message);
+            }
+        });
+    }
+
+    public T receive() {
+        //in case one wants to synchronize this method,
+        //bear in mind that it is blocking so it cannot share the same lock as others
+        return delegate.receive();
+    }
+
+    public void stop() {
+        delegate.stop();
+    }
+
+    public String toString() {
+        return delegate.toString();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecuter.java
index a15f558..6db1b4b 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecuter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecuter.java
@@ -15,10 +15,9 @@
  */
 package org.gradle.launcher.daemon.server.exec;
 
+import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.protocol.Command;
 import org.gradle.messaging.remote.internal.Connection;
-import org.gradle.launcher.daemon.server.DaemonStateCoordinator;
-import org.gradle.launcher.daemon.context.DaemonContext;
 
 /**
  * An object capable of responding to commands sent to a daemon.
@@ -37,5 +36,5 @@ public interface DaemonCommandExecuter {
      * <p>
      * The {@code command} param may be {@code null}, which means the client disconnected before sending a command.
      */
-    void executeCommand(Connection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateCoordinator daemonStateCoordinator);
+    void executeCommand(Connection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl);
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java
index 596e21e..8020d83 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java
@@ -17,7 +17,6 @@ package org.gradle.launcher.daemon.server.exec;
 
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.protocol.Command;
-import org.gradle.launcher.daemon.server.DaemonStateCoordinator;
 import org.gradle.messaging.remote.internal.DisconnectAwareConnection;
 
 import java.util.LinkedList;
@@ -36,18 +35,18 @@ public class DaemonCommandExecution {
     final private DisconnectAwareConnection<Object> connection;
     final private Command command;
     final private DaemonContext daemonContext;
-    final private DaemonStateCoordinator daemonStateCoordinator;
+    final private DaemonStateControl daemonStateControl;
     final private List<DaemonCommandAction> actions;
 
     private Throwable exception;
     private Object result;
     private final List<Runnable> finalizers = new LinkedList<Runnable>();
 
-    public DaemonCommandExecution(DisconnectAwareConnection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateCoordinator daemonStateCoordinator, List<DaemonCommandAction> actions) {
+    public DaemonCommandExecution(DisconnectAwareConnection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl, List<DaemonCommandAction> actions) {
         this.connection = connection;
         this.command = command;
         this.daemonContext = daemonContext;
-        this.daemonStateCoordinator = daemonStateCoordinator;
+        this.daemonStateControl = daemonStateControl;
 
         this.actions = new LinkedList<DaemonCommandAction>(actions);
     }
@@ -69,8 +68,8 @@ public class DaemonCommandExecution {
         return daemonContext;
     }
 
-    public DaemonStateCoordinator getDaemonStateCoordinator() {
-        return daemonStateCoordinator;
+    public DaemonStateControl getDaemonStateControl() {
+        return daemonStateControl;
     }
 
     /**
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonStateControl.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonStateControl.java
new file mode 100644
index 0000000..5ad1e6c
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonStateControl.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.server.exec;
+
+public interface DaemonStateControl {
+    /**
+     * Perform a stop, but wait until the daemon is idle to cut any open connections.
+     *
+     * The daemon will be removed from the registry upon calling this regardless of whether it is busy or not.
+     * If it is idle, this method will block until the daemon fully stops.
+     *
+     * If the daemon is busy, this method will return after removing the daemon from the registry but before the
+     * daemon is fully stopped. In this case, the daemon will stop as soon as {@link #onFinishCommand()} is called.
+     */
+    void stopAsSoonAsIdle();
+
+    /**
+     * @return returns false if the daemon was already requested to stop
+     */
+    boolean requestStop();
+
+    /**
+     * Called when the execution of a command begins.
+     * <p>
+     * If the daemon is busy (i.e. already executing a command), this method will return the existing
+     * execution which the caller should be prepared for without considering the given execution to be in progress.
+     * If the daemon is idle the return value will be {@code null} and the given execution will be considered in progress.
+     */
+    DaemonCommandExecution onStartCommand(DaemonCommandExecution execution);
+
+    /**
+     * Called when the execution of a command is complete (or at least the daemon is available for new commands).
+     * <p>
+     * If the daemon is currently idle, this method will return {@code null}. If it is busy, it will return what was the
+     * current execution which will considered to be complete (putting the daemon back in idle state).
+     * <p>
+     * If {@link #stopAsSoonAsIdle()} was previously called, this method will block while the daemon stops.
+     */
+    DaemonCommandExecution onFinishCommand();
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DefaultDaemonCommandExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DefaultDaemonCommandExecuter.java
index 5c7e7f2..75eec96 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DefaultDaemonCommandExecuter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DefaultDaemonCommandExecuter.java
@@ -16,13 +16,12 @@
 package org.gradle.launcher.daemon.server.exec;
 
 import org.gradle.initialization.GradleLauncherFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.launcher.daemon.context.DaemonContext;
 import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.launcher.daemon.protocol.Command;
-import org.gradle.launcher.daemon.server.DaemonStateCoordinator;
 import org.gradle.logging.LoggingManagerInternal;
-import org.gradle.messaging.concurrent.ExecutorFactory;
 import org.gradle.messaging.remote.internal.Connection;
 import org.gradle.messaging.remote.internal.DisconnectAwareConnectionDecorator;
 
@@ -51,12 +50,12 @@ public class DefaultDaemonCommandExecuter implements DaemonCommandExecuter {
         this.launcherFactory = launcherFactory;
     }
 
-    public void executeCommand(Connection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateCoordinator daemonStateCoordinator) {
+    public void executeCommand(Connection<Object> connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl) {
         new DaemonCommandExecution(
             new DisconnectAwareConnectionDecorator<Object>(connection, executorFactory.create("DefaultDaemonCommandExecuter > DisconnectAwareConnectionDecorator")),
             command,
             daemonContext,
-            daemonStateCoordinator,
+            daemonStateControl,
             createActions(daemonContext)
         ).proceed();
     }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java
index 6cf77d3..093c5ef 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ExecuteBuild.java
@@ -15,12 +15,12 @@
  */
 package org.gradle.launcher.daemon.server.exec;
 
-import org.gradle.api.GradleException;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.initialization.GradleLauncherFactory;
 import org.gradle.launcher.daemon.logging.DaemonMessages;
 import org.gradle.launcher.daemon.protocol.Build;
+import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter;
 import org.gradle.launcher.exec.ReportedException;
 
 /**
@@ -40,10 +40,10 @@ public class ExecuteBuild extends BuildCommandOnly {
 
     protected void doBuild(DaemonCommandExecution execution, Build build) {
         LOGGER.info("Executing build with daemon context: {}", execution.getDaemonContext());
-        
+        InProcessGradleLauncherActionExecuter executer = new InProcessGradleLauncherActionExecuter(launcherFactory);
         try {
-            execution.setResult(build.run(launcherFactory));
-        } catch (GradleException e) {
+            execution.setResult(executer.execute(build.getAction(), build.getParameters()));
+        } catch (ReportedException e) {
             /*
                 We have to wrap in a ReportedException so the other side doesn't re-log this exception, because it's already
                 been logged by the GradleLauncher infrastructure, and that logging has been shipped over to the other side.
@@ -51,7 +51,7 @@ public class ExecuteBuild extends BuildCommandOnly {
                 This doesn't seem right. Perhaps we should assume on the client side that all “build failures” (opposed to daemon infrastructure failures)
                 have already been logged and do away with this wrapper.
             */
-            execution.setException(new ReportedException(e));
+            execution.setException(e);
         } finally {
             LOGGER.debug(DaemonMessages.FINISHED_BUILD);
         }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ForwardClientInput.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ForwardClientInput.java
index 6572d15..7381284 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ForwardClientInput.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/ForwardClientInput.java
@@ -19,10 +19,10 @@ import org.gradle.api.GradleException;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.launcher.daemon.protocol.CloseInput;
 import org.gradle.launcher.daemon.protocol.ForwardInput;
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
 import org.gradle.messaging.dispatch.AsyncReceive;
 import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.util.StdinSwapper;
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleStop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleStop.java
index ea6596a..2c912f8 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleStop.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/HandleStop.java
@@ -29,7 +29,7 @@ public class HandleStop implements DaemonCommandAction {
                 DaemonMain#doAction will throw a DaemonStoppedException. Note that at this point we will also 
                 immediately tear down the client connection and remove the daemon from the registry.
             */
-            execution.getDaemonStateCoordinator().requestStop();
+            execution.getDaemonStateControl().requestStop();
         } else {
             execution.proceed();
         }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java
index 6dd39ea..c4b6795 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/LogToClient.java
@@ -38,7 +38,7 @@ class LogToClient extends BuildCommandOnly {
     }
 
     protected void doBuild(final DaemonCommandExecution execution, Build build) {
-        final LogLevel buildLogLevel = build.getStartParameter().getLogLevel();
+        final LogLevel buildLogLevel = build.getParameters().getLogLevel();
         OutputEventListener listener = new OutputEventListener() {
             public void onOutput(OutputEvent event) {
                 try {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartBuildOrRespondWithBusy.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartBuildOrRespondWithBusy.java
index 4edb5fe..0ed95e8 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartBuildOrRespondWithBusy.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartBuildOrRespondWithBusy.java
@@ -21,7 +21,6 @@ import org.gradle.launcher.daemon.diagnostics.DaemonDiagnostics;
 import org.gradle.launcher.daemon.protocol.Build;
 import org.gradle.launcher.daemon.protocol.BuildStarted;
 import org.gradle.launcher.daemon.protocol.DaemonBusy;
-import org.gradle.launcher.daemon.server.DaemonStateCoordinator;
 
 /**
  * Updates the daemon idle/busy status, sending a DaemonBusy result back to the client if the daemon is busy.
@@ -36,7 +35,7 @@ public class StartBuildOrRespondWithBusy extends BuildCommandOnly {
     }
 
     protected void doBuild(DaemonCommandExecution execution, Build build) {
-        DaemonStateCoordinator stateCoordinator = execution.getDaemonStateCoordinator();
+        DaemonStateControl stateCoordinator = execution.getDaemonStateControl();
 
         DaemonCommandExecution existingExecution = stateCoordinator.onStartCommand(execution);
         if (existingExecution != null) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartStopIfBuildAndStop.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartStopIfBuildAndStop.java
index bb52ac1..ba64ca6 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartStopIfBuildAndStop.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/StartStopIfBuildAndStop.java
@@ -32,7 +32,7 @@ public class StartStopIfBuildAndStop implements DaemonCommandAction {
             
             // This will cause the daemon to be removed from the registry, but no close the connection
             // to the client until we've sent back the result.
-            execution.getDaemonStateCoordinator().stopAsSoonAsIdle();
+            execution.getDaemonStateControl().stopAsSoonAsIdle();
         }
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/WatchForDisconnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/WatchForDisconnection.java
index c5ed48c..6835aa7 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/WatchForDisconnection.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/WatchForDisconnection.java
@@ -37,7 +37,7 @@ public class WatchForDisconnection implements DaemonCommandAction {
                     DaemonMain#doAction will throw a DaemonStoppedException. Note that at this point we will also 
                     immediately remove the daemon from the registry.
                 */
-                execution.getDaemonStateCoordinator().requestStop();
+                execution.getDaemonStateControl().requestStop();
             }
         });
 
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java
index 9b82e32..7ec80d0 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/BuildActionParameters.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.launcher.exec;
 
-import org.gradle.initialization.BuildClientMetaData;
+import org.gradle.api.logging.LogLevel;
 import org.gradle.initialization.BuildRequestMetaData;
 
 import java.io.File;
@@ -25,11 +25,11 @@ import java.util.Map;
 public interface BuildActionParameters extends Serializable {
     BuildRequestMetaData getBuildRequestMetaData();
 
-    BuildClientMetaData getClientMetaData();
-
     Map<String, String> getSystemProperties();
 
     Map<String, String> getEnvVariables();
 
     File getCurrentDir();
+
+    LogLevel getLogLevel();
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java
index de06dea..9c8c234 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/DefaultBuildActionParameters.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.launcher.exec;
 
+import org.gradle.api.logging.LogLevel;
 import org.gradle.initialization.BuildClientMetaData;
 import org.gradle.initialization.BuildRequestMetaData;
 import org.gradle.initialization.DefaultBuildRequestMetaData;
@@ -29,13 +30,15 @@ public class DefaultBuildActionParameters implements BuildActionParameters, Seri
     private final BuildClientMetaData clientMetaData;
     private final long startTime;
     private final File currentDir;
+    private final LogLevel logLevel;
     private final Map<String, String> systemProperties;
     private final Map<String, String> envVariables;
 
-    public DefaultBuildActionParameters(BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties, Map<String, String> envVariables, File currentDir) {
+    public DefaultBuildActionParameters(BuildClientMetaData clientMetaData, long startTime, Map<?, ?> systemProperties, Map<String, String> envVariables, File currentDir, LogLevel logLevel) {
         this.clientMetaData = clientMetaData;
         this.startTime = startTime;
         this.currentDir = currentDir;
+        this.logLevel = logLevel;
         assert systemProperties != null;
         assert envVariables != null;
         this.systemProperties = new HashMap<String, String>();
@@ -47,10 +50,6 @@ public class DefaultBuildActionParameters implements BuildActionParameters, Seri
         return new DefaultBuildRequestMetaData(clientMetaData, startTime);
     }
 
-    public BuildClientMetaData getClientMetaData() {
-        return clientMetaData;
-    }
-
     public Map<String, String> getSystemProperties() {
         return systemProperties;
     }
@@ -63,6 +62,10 @@ public class DefaultBuildActionParameters implements BuildActionParameters, Seri
         return currentDir;
     }
 
+    public LogLevel getLogLevel() {
+        return logLevel;
+    }
+
     @Override
     public String toString() {
         return "DefaultBuildActionParameters{"
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/EntryPoint.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/EntryPoint.java
deleted file mode 100644
index 4e7738d..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/EntryPoint.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.launcher.exec;
-
-import org.gradle.BuildExceptionReporter;
-import org.gradle.api.Action;
-import org.gradle.configuration.GradleLauncherMetaData;
-import org.gradle.logging.LoggingConfiguration;
-import org.gradle.logging.internal.StreamingStyledTextOutputFactory;
-
-/**
- * An entry point is the point at which execution will never return from.
- * <p>
- * It's purpose is to consistently apply our completion logic of forcing the JVM
- * to exit at a certain point instead of waiting for all threads to die, and to provide
- * some consistent unhandled exception catching.
- * <p>
- * Entry points may be nested, as is the case when a foreground daemon is started.
- * <p>
- * The createCompleter() and createErrorHandler() are not really intended to be overridden
- * by subclasses as they define our entry point behaviour, but they are protected to enable
- * testing as it's difficult to test something that will call System.exit().
- */
-abstract public class EntryPoint implements Runnable {
-
-    /**
-     * Unless the createCompleter() method is overridden, the JVM will exit before returning from this method.
-     */
-    public void run() {
-        RecordingExecutionListener listener = new RecordingExecutionListener();
-        try {
-            doAction(listener);
-        } catch (Throwable e) {
-            createErrorHandler().execute(e);
-            listener.onFailure(e);
-        }
-
-        Throwable failure = listener.getFailure();
-        ExecutionCompleter completer = createCompleter();
-        if (failure == null) {
-            completer.complete();
-        } else {
-            completer.completeWithFailure(failure);
-        }
-    }
-
-    protected ExecutionCompleter createCompleter() {
-        return new ProcessCompleter();
-    }
-
-    protected Action<Throwable> createErrorHandler() {
-        return new BuildExceptionReporter(new StreamingStyledTextOutputFactory(System.err), new LoggingConfiguration(), new GradleLauncherMetaData());
-    }
-
-    protected abstract void doAction(ExecutionListener listener);
-
-    private static class RecordingExecutionListener implements ExecutionListener {
-        private Throwable failure;
-
-        public void onFailure(Throwable failure) {
-            this.failure = failure;
-        }
-
-        public Throwable getFailure() {
-            return failure;
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExceptionReportingAction.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExceptionReportingAction.java
deleted file mode 100644
index 65fa96f..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExceptionReportingAction.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.launcher.exec;
-
-import org.gradle.api.Action;
-
-public class ExceptionReportingAction implements Action<ExecutionListener> {
-    private final Action<ExecutionListener> action;
-    private final Action<Throwable> reporter;
-
-    public ExceptionReportingAction(Action<ExecutionListener> action, Action<Throwable> reporter) {
-        this.action = action;
-        this.reporter = reporter;
-    }
-
-    public void execute(ExecutionListener executionListener) {
-        try {
-            action.execute(executionListener);
-        } catch (ReportedException e) {
-            executionListener.onFailure(e.getCause());
-        } catch (Throwable t) {
-            reporter.execute(t);
-            executionListener.onFailure(t);
-        }
-    }
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionCompleter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionCompleter.java
deleted file mode 100644
index 3bb1db1..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionCompleter.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.launcher.exec;
-
-public interface ExecutionCompleter {
-    void complete();
-    void completeWithFailure(Throwable t);
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionListener.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionListener.java
deleted file mode 100644
index 89f6617..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ExecutionListener.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.launcher.exec;
-
-/**
- * Allows an execution action to provide status information to the execution context.
- *
- * <p>Note: if the action does not call {@link #onFailure(Throwable)}, then the execution is assumed to have
- * succeeded.</p>
- */
-public interface ExecutionListener {
-    /**
-     * Reports a failure of the execution. Note that it is the caller's responsibility to perform any logging of the
-     * failure.
-     *
-     * @param failure The execution failure. This exception has already been logged.
-     */
-    void onFailure(Throwable failure);
-}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuter.java
new file mode 100644
index 0000000..e9feb3e
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.exec;
+
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.initialization.GradleLauncherFactory;
+
+public class InProcessGradleLauncherActionExecuter implements GradleLauncherActionExecuter<BuildActionParameters> {
+    private final GradleLauncherFactory gradleLauncherFactory;
+
+    public InProcessGradleLauncherActionExecuter(GradleLauncherFactory gradleLauncherFactory) {
+        this.gradleLauncherFactory = gradleLauncherFactory;
+    }
+
+    public <T> T execute(GradleLauncherAction<T> action, BuildActionParameters actionParameters) {
+        StartParameter startParameter = new StartParameter();
+        if (action instanceof InitializationAware) {
+            InitializationAware initializationAware = (InitializationAware) action;
+            startParameter = initializationAware.configureStartParameter();
+        }
+        GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameter, actionParameters.getBuildRequestMetaData());
+        BuildResult buildResult = action.run(gradleLauncher);
+        Throwable failure = buildResult.getFailure();
+        if (failure != null) {
+            throw new ReportedException(failure);
+        }
+        return action.getResult();
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ProcessCompleter.java b/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ProcessCompleter.java
deleted file mode 100644
index 9ffb564..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/exec/ProcessCompleter.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.launcher.exec;
-
-public class ProcessCompleter implements ExecutionCompleter {
-    public void complete() {
-        System.exit(0);
-    }
-
-    public void completeWithFailure(Throwable t) {
-        System.exit(1);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
index 215cb9a..3012e5f 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
@@ -17,7 +17,6 @@ package org.gradle.tooling.internal.provider;
 
 import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.launcher.daemon.client.DaemonClient;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.exec.BuildActionParameters;
 import org.gradle.launcher.exec.DefaultBuildActionParameters;
@@ -29,19 +28,19 @@ import org.gradle.tooling.internal.provider.input.ProviderOperationParameters;
 import java.io.File;
 
 public class DaemonGradleLauncherActionExecuter implements GradleLauncherActionExecuter<ProviderOperationParameters> {
-    private final DaemonClient client;
+    private final GradleLauncherActionExecuter<BuildActionParameters> executer;
     private final DaemonParameters parameters;
 
-    public DaemonGradleLauncherActionExecuter(DaemonClient client, DaemonParameters parameters) {
-        this.client = client;
+    public DaemonGradleLauncherActionExecuter(GradleLauncherActionExecuter<BuildActionParameters> executer, DaemonParameters parameters) {
+        this.executer = executer;
         this.parameters = parameters;
     }
 
     public <T> T execute(GradleLauncherAction<T> action, ProviderOperationParameters actionParameters) {
         BuildActionParameters parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), actionParameters.getStartTime(),
-                this.parameters.getEffectiveSystemProperties(), System.getenv(), new File(System.getProperty("user.dir")));
+                this.parameters.getEffectiveSystemProperties(), System.getenv(), new File(System.getProperty("user.dir")), actionParameters.getBuildLogLevel());
         try {
-            return client.execute(action, parameters);
+            return executer.execute(action, parameters);
         } catch (ReportedException e) {
             throw new BuildExceptionVersion1(e.getCause());
         }
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
index bd6bf05..fd6be80 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
@@ -22,11 +22,12 @@ import org.gradle.internal.Factory;
 import org.gradle.launcher.daemon.client.DaemonClient;
 import org.gradle.launcher.daemon.client.DaemonClientServices;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.exec.BuildActionParameters;
 import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.logging.LoggingManagerInternal;
 import org.gradle.logging.LoggingServiceRegistry;
 import org.gradle.logging.internal.OutputEventRenderer;
-import org.gradle.logging.internal.slf4j.SimpleSlf4jLoggingConfigurer;
+import org.gradle.logging.internal.logback.SimpleLogbackLoggingConfigurer;
 import org.gradle.tooling.internal.build.DefaultBuildEnvironment;
 import org.gradle.tooling.internal.protocol.*;
 import org.gradle.tooling.internal.provider.input.AdaptedOperationParameters;
@@ -42,12 +43,12 @@ import java.util.List;
 public class DefaultConnection implements InternalConnection {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConnection.class);
     private final EmbeddedExecuterSupport embeddedExecuterSupport;
-    private final SimpleSlf4jLoggingConfigurer loggingConfigurer = new SimpleSlf4jLoggingConfigurer();
+    private final SimpleLogbackLoggingConfigurer loggingConfigurer = new SimpleLogbackLoggingConfigurer();
 
     public DefaultConnection() {
         LOGGER.debug("Provider implementation created.");
         //embedded use of the tooling api is not supported publicly so we don't care about its thread safety
-        //we can keep still keep this state:
+        //we can still keep this state:
         embeddedExecuterSupport = new EmbeddedExecuterSupport();
         LOGGER.debug("Embedded executer support created.");
     }
@@ -114,22 +115,20 @@ public class DefaultConnection implements InternalConnection {
     }
 
     private GradleLauncherActionExecuter<ProviderOperationParameters> createExecuter(ProviderOperationParameters operationParameters) {
+        LoggingServiceRegistry loggingServices;
+        DaemonParameters daemonParams = init(operationParameters);
+        GradleLauncherActionExecuter<BuildActionParameters> executer;
         if (Boolean.TRUE.equals(operationParameters.isEmbedded())) {
-            return embeddedExecuterSupport.getExecuter();
+            loggingServices = embeddedExecuterSupport.getLoggingServices();
+            executer = embeddedExecuterSupport.getExecuter();
         } else {
-            LoggingServiceRegistry loggingServices = LoggingServiceRegistry.newEmbeddableLogging();
-
+            loggingServices = LoggingServiceRegistry.newEmbeddableLogging();
             loggingServices.get(OutputEventRenderer.class).configure(operationParameters.getBuildLogLevel());
-
-            DaemonParameters daemonParams = init(operationParameters);
             DaemonClientServices clientServices = new DaemonClientServices(loggingServices, daemonParams, operationParameters.getStandardInput());
-            DaemonClient client = clientServices.get(DaemonClient.class);
-
-            GradleLauncherActionExecuter<ProviderOperationParameters> executer = new DaemonGradleLauncherActionExecuter(client, clientServices.getDaemonParameters());
-
-            Factory<LoggingManagerInternal> loggingManagerFactory = clientServices.getLoggingServices().getFactory(LoggingManagerInternal.class);
-            return new LoggingBridgingGradleLauncherActionExecuter(executer, loggingManagerFactory);
+            executer = clientServices.get(DaemonClient.class);
         }
+        Factory<LoggingManagerInternal> loggingManagerFactory = loggingServices.getFactory(LoggingManagerInternal.class);
+        return new LoggingBridgingGradleLauncherActionExecuter(new DaemonGradleLauncherActionExecuter(executer, daemonParams), loggingManagerFactory);
     }
 
     private DaemonParameters init(ProviderOperationParameters operationParameters) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java
index 6bedc88..da96a62 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedExecuterSupport.java
@@ -16,9 +16,10 @@
 
 package org.gradle.tooling.internal.provider;
 
-import org.gradle.internal.Factory;
 import org.gradle.initialization.DefaultGradleLauncherFactory;
-import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.launcher.exec.BuildActionParameters;
+import org.gradle.launcher.exec.GradleLauncherActionExecuter;
+import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter;
 import org.gradle.logging.LoggingServiceRegistry;
 
 /**
@@ -34,9 +35,11 @@ public class EmbeddedExecuterSupport {
         gradleLauncherFactory = new DefaultGradleLauncherFactory(embeddedLogging);
     }
 
-    public LoggingBridgingGradleLauncherActionExecuter getExecuter() {
-        EmbeddedGradleLauncherActionExecuter executer = new EmbeddedGradleLauncherActionExecuter(gradleLauncherFactory);
-        Factory<LoggingManagerInternal> loggingManagerFactory = embeddedLogging.getFactory(LoggingManagerInternal.class);
-        return new LoggingBridgingGradleLauncherActionExecuter(executer, loggingManagerFactory);
+    public GradleLauncherActionExecuter<BuildActionParameters> getExecuter() {
+        return new InProcessGradleLauncherActionExecuter(gradleLauncherFactory);
+    }
+
+    public LoggingServiceRegistry getLoggingServices() {
+        return embeddedLogging;
     }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuter.java
deleted file mode 100644
index 3727df2..0000000
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.tooling.internal.provider;
-
-import org.gradle.BuildResult;
-import org.gradle.GradleLauncher;
-import org.gradle.StartParameter;
-import org.gradle.initialization.GradleLauncherAction;
-import org.gradle.initialization.GradleLauncherFactory;
-import org.gradle.launcher.exec.GradleLauncherActionExecuter;
-import org.gradle.launcher.exec.InitializationAware;
-import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
-import org.gradle.tooling.internal.provider.input.ProviderOperationParameters;
-
-/**
- * A {@link GradleLauncherActionExecuter} which executes an action locally.
- */
-public class EmbeddedGradleLauncherActionExecuter implements GradleLauncherActionExecuter<ProviderOperationParameters> {
-    private final GradleLauncherFactory gradleLauncherFactory;
-
-    public EmbeddedGradleLauncherActionExecuter(GradleLauncherFactory gradleLauncherFactory) {
-        this.gradleLauncherFactory = gradleLauncherFactory;
-    }
-
-    public <T> T execute(GradleLauncherAction<T> action, ProviderOperationParameters actionParameters) {
-        StartParameter startParameter;
-        if (action instanceof InitializationAware) {
-            InitializationAware initializationAware = (InitializationAware) action;
-            startParameter = initializationAware.configureStartParameter();
-        } else {
-            startParameter = new StartParameter();
-        }
-        GradleLauncher gradleLauncher = gradleLauncherFactory.newInstance(startParameter);
-        BuildResult result = action.run(gradleLauncher);
-        if (result.getFailure() != null) {
-            throw new BuildExceptionVersion1(result.getFailure());
-        }
-        return action.getResult();
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
index c4173e0..8ba8b67 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/MainTest.groovy
@@ -20,8 +20,8 @@ import org.junit.Rule
 import org.gradle.util.RedirectStdOutAndErr
 import org.gradle.api.Action
 import org.gradle.launcher.cli.CommandLineActionFactory
-import org.gradle.launcher.exec.ExecutionListener
-import org.gradle.launcher.exec.ExecutionCompleter
+import org.gradle.launcher.bootstrap.ExecutionListener
+import org.gradle.launcher.bootstrap.ExecutionCompleter
 
 class MainTest extends Specification {
     
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/bootstrap/EntryPointTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/bootstrap/EntryPointTest.groovy
new file mode 100644
index 0000000..c67e417
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/bootstrap/EntryPointTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.bootstrap
+
+import spock.lang.Specification
+import org.gradle.api.Action
+
+class EntryPointTest extends Specification {
+    final Action<ExecutionListener> action = Mock()
+    final ProcessCompleter completer = Mock()
+    final EntryPoint entryPoint = new EntryPoint() {
+        @Override
+        protected ExecutionCompleter createCompleter() {
+            return completer
+        }
+
+        @Override
+        protected void doAction(ExecutionListener listener) {
+            action.execute(listener)
+        }
+    }
+
+    def "exits with success when action executes successfully"() {
+        when:
+        entryPoint.run()
+
+        then:
+        1 * action.execute(!null)
+        1 * completer.complete()
+        0 * _._
+    }
+
+    def "exits with failure when action reports a failure"() {
+        def failure = new RuntimeException()
+
+        when:
+        entryPoint.run()
+
+        then:
+        1 * action.execute(!null) >> { ExecutionListener listener -> listener.onFailure(failure) }
+        1 * completer.completeWithFailure(failure)
+        0 * _._
+    }
+
+    def "exits with failure when action throws exception"() {
+        def failure = new RuntimeException()
+
+        when:
+        entryPoint.run()
+
+        then:
+        1 * action.execute(!null) >> { throw failure }
+        1 * completer.completeWithFailure(failure)
+        0 * _._
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
index ef0e940..c858a72 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/BuildActionsFactoryTest.groovy
@@ -27,6 +27,10 @@ import org.gradle.util.SetSystemProperties
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.launcher.daemon.client.SingleUseDaemonClient
+import org.gradle.launcher.daemon.client.DaemonClient
+import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter
 
 class BuildActionsFactoryTest extends Specification {
     @Rule
@@ -39,6 +43,7 @@ class BuildActionsFactoryTest extends Specification {
 
     def setup() {        
         _ * loggingServices.get(OutputEventListener) >> Mock(OutputEventListener)
+        _ * loggingServices.get(ProgressLoggerFactory) >> Mock(ProgressLoggerFactory)
     }
 
     def executesBuild() {
@@ -183,17 +188,21 @@ class BuildActionsFactoryTest extends Specification {
         return factory.createAction(parser, cl)
     }
 
-    def isDaemon(def action) {
+    void isDaemon(def action) {
         assert action instanceof ActionAdapter
-        action.action instanceof DaemonBuildAction
+        assert action.action instanceof RunBuildAction
+        assert action.action.executer instanceof DaemonClient
     }
 
-    def isInProcess(def action) {
-        action instanceof RunBuildAction
+    void isInProcess(def action) {
+        assert action instanceof ActionAdapter
+        assert action.action instanceof RunBuildAction
+        assert action.action.executer instanceof InProcessGradleLauncherActionExecuter
     }
 
-    def isSingleUseDaemon(def action) {
+    void isSingleUseDaemon(def action) {
         assert action instanceof ActionAdapter
-        action.action instanceof DaemonBuildAction
+        assert action.action instanceof RunBuildAction
+        assert action.action.executer instanceof SingleUseDaemonClient
     }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
index 3f0eae5..d29a4df 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/CommandLineActionFactoryTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.cli.CommandLineConverter
 import org.gradle.cli.CommandLineParser
 import org.gradle.internal.Factory
 import org.gradle.internal.service.ServiceRegistry
-import org.gradle.launcher.exec.ExecutionListener
+import org.gradle.launcher.bootstrap.ExecutionListener
 import org.gradle.logging.internal.OutputEventListener
 import org.gradle.logging.internal.StreamingStyledTextOutput
 import org.gradle.util.GradleVersion
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/DaemonBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/DaemonBuildActionTest.groovy
deleted file mode 100644
index 224f024..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/DaemonBuildActionTest.groovy
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.launcher.cli
-
-import org.gradle.StartParameter
-import org.gradle.initialization.BuildClientMetaData
-import org.gradle.launcher.exec.BuildActionParameters
-import org.gradle.launcher.exec.GradleLauncherActionExecuter
-import spock.lang.Specification
-
-class DaemonBuildActionTest extends Specification {
-    final GradleLauncherActionExecuter<BuildActionParameters> client = Mock()
-    final StartParameter startParameter = Mock()
-    final BuildClientMetaData clientMetaData = Mock()
-    final File currentDir = new File('current-dir')
-    final long startTime = 90
-    final Map<String, String> systemProperties = [key: 'value']
-    final Map<String, String> envVariables = [key2: 'value2']
-    final DaemonBuildAction action = new DaemonBuildAction(client, startParameter, currentDir, clientMetaData, startTime, systemProperties, envVariables)
-
-    def runsBuildUsingDaemon() {
-        when:
-        action.run()
-
-        then:
-        1 * client.execute({!null}, {!null}) >> { args ->
-            ExecuteBuildAction action = args[0]
-            assert action.startParameter == startParameter
-            BuildActionParameters build = args[1]
-            assert build.clientMetaData == clientMetaData
-            assert build.startTime == startTime
-            assert build.systemProperties == systemProperties
-        }
-        0 * _._
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/ExceptionReportingActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/ExceptionReportingActionTest.groovy
new file mode 100644
index 0000000..86730b2
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/ExceptionReportingActionTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 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 org.gradle.launcher.cli
+
+import org.gradle.api.Action
+import spock.lang.Specification
+import org.gradle.launcher.bootstrap.ExecutionListener
+import org.gradle.launcher.exec.ReportedException
+
+class ExceptionReportingActionTest extends Specification {
+    final Action<ExecutionListener> target = Mock()
+    final ExecutionListener listener = Mock()
+    final Action<Throwable> reporter = Mock()
+    final ExceptionReportingAction action = new ExceptionReportingAction(target, reporter)
+
+    def executesAction() {
+        when:
+        action.execute(listener)
+
+        then:
+        1 * target.execute(listener)
+        0 * _._
+    }
+
+    def reportsExceptionThrownByAction() {
+        def failure = new RuntimeException()
+
+        when:
+        action.execute(listener)
+
+        then:
+        1 * target.execute(listener) >> { throw failure }
+        1 * reporter.execute(failure)
+        1 * listener.onFailure(failure)
+        0 * _._
+    }
+
+    def doesNotReportAlreadyReportedExceptionThrownByAction() {
+        def cause = new RuntimeException()
+        def failure = new ReportedException(cause)
+
+        when:
+        action.execute(listener)
+
+        then:
+        1 * target.execute(listener) >> { throw failure }
+        1 * listener.onFailure(cause)
+        0 * _._
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy
index b995da0..eca6cc0 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/cli/RunBuildActionTest.groovy
@@ -15,53 +15,37 @@
  */
 package org.gradle.launcher.cli
 
-import spock.lang.Specification
 import org.gradle.StartParameter
-import org.gradle.internal.service.ServiceRegistry
-import org.gradle.initialization.GradleLauncherFactory
-import org.gradle.GradleLauncher
-import org.gradle.BuildResult
-import org.gradle.initialization.BuildRequestMetaData
-import org.gradle.launcher.exec.ExecutionListener
+import org.gradle.initialization.BuildClientMetaData
+import org.gradle.launcher.exec.BuildActionParameters
+import org.gradle.launcher.exec.GradleLauncherActionExecuter
+import spock.lang.Specification
+import org.gradle.api.logging.LogLevel
 
 class RunBuildActionTest extends Specification {
-    final StartParameter startParameter = new StartParameter()
-    final ServiceRegistry loggingServices = Mock()
-    final GradleLauncherFactory gradleLauncherFactory = Mock()
-    final ExecutionListener completer = Mock()
-    final GradleLauncher launcher = Mock()
-    final BuildResult result = Mock()
-    final BuildRequestMetaData requestMetaData = Mock()
-    final RunBuildAction action = new RunBuildAction(startParameter, loggingServices, requestMetaData) {
-        @Override
-        GradleLauncherFactory createGradleLauncherFactory(ServiceRegistry loggingServices) {
-            return gradleLauncherFactory
-        }
-    }
-
-    def executesBuild() {
-        when:
-        action.execute(completer)
-
-        then:
-        1 * gradleLauncherFactory.newInstance(startParameter, requestMetaData) >> launcher
-        1 * launcher.run() >> result
-        _ * result.failure >> null
-        0 * _._
-    }
-
-    def executesFailedBuild() {
-        def failure = new RuntimeException()
-
+    final GradleLauncherActionExecuter<BuildActionParameters> client = Mock()
+    final StartParameter startParameter = Mock()
+    final BuildClientMetaData clientMetaData = Mock()
+    final File currentDir = new File('current-dir')
+    final long startTime = 90
+    final Map<String, String> systemProperties = [key: 'value']
+    final Map<String, String> envVariables = [key2: 'value2']
+    final RunBuildAction action = new RunBuildAction(client, startParameter, currentDir, clientMetaData, startTime, systemProperties, envVariables)
+
+    def runsBuildUsingDaemon() {
         when:
-        action.execute(completer)
+        action.run()
 
         then:
-        1 * gradleLauncherFactory.newInstance(startParameter, requestMetaData) >> launcher
-        1 * launcher.run() >> result
-        _ * result.failure >> failure
-        1 * completer.onFailure(failure)
+        startParameter.logLevel >> LogLevel.ERROR
+        1 * client.execute({!null}, {!null}) >> { args ->
+            ExecuteBuildAction action = args[0]
+            assert action.startParameter == startParameter
+            BuildActionParameters build = args[1]
+            assert build.clientMetaData == clientMetaData
+            assert build.startTime == startTime
+            assert build.systemProperties == systemProperties
+        }
         0 * _._
     }
-
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/DaemonExecHandleBuilderSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/DaemonExecHandleBuilderSpec.groovy
new file mode 100644
index 0000000..2fb3ff3
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/DaemonExecHandleBuilderSpec.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon
+
+import org.gradle.launcher.daemon.bootstrap.DaemonOutputConsumer
+import org.gradle.process.internal.ExecHandleBuilder
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 5/7/12
+ */
+class DaemonExecHandleBuilderSpec extends Specification {
+
+    def builder = Mock(ExecHandleBuilder)
+    def daemonBuilder = new DaemonExecHandleBuilder(builder: builder)
+
+    def "creates process with correct settings"() {
+        when:
+        daemonBuilder.build(["java", "-cp"], new File("foo"), Mock(DaemonOutputConsumer))
+
+        then:
+        //integ test coverage for certain below settings is either not easy or not obvious
+        1 * builder.setTimeout(30000)
+        1 * builder.redirectErrorStream()
+        1 * builder.setWorkingDir(new File("foo"))
+        1 * builder.commandLine(["java", "-cp"])
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
index f08ef7c..228a6f1 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/EmbeddedDaemonSmokeTest.groovy
@@ -24,6 +24,7 @@ import org.gradle.tooling.internal.provider.ExecuteBuildAction
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.api.logging.LogLevel
 
 /**
  * Exercises the basic mechanics using an embedded daemon.
@@ -41,7 +42,7 @@ class EmbeddedDaemonSmokeTest extends Specification {
         given:
         def action = new ConfiguringBuildAction(projectDirectory: temp.dir, searchUpwards: false, tasks: ['echo'],
                 gradleUserHomeDir: temp.createDir("user-home"), action: new ExecuteBuildAction())
-        def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), new Date().time, System.properties, System.getenv(), temp.dir)
+        def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), new Date().time, System.properties, System.getenv(), temp.dir, LogLevel.LIFECYCLE)
         
         and:
         def outputFile = temp.file("output.txt")
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonGreeterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonGreeterTest.groovy
new file mode 100644
index 0000000..662ea81
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonGreeterTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.bootstrap
+
+import org.gradle.api.GradleException
+import org.gradle.api.internal.DocumentationRegistry
+import org.gradle.launcher.daemon.logging.DaemonMessages
+import org.gradle.process.ExecResult
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 4/10/12
+ */
+class DaemonGreeterTest extends Specification {
+
+    DocumentationRegistry registry = Mock()
+
+    def "parses the process output"() {
+        given:
+        def output = """hey joe!
+another line of output...
+${new DaemonStartupCommunication().daemonStartedMessage(12, new File("12.log"))}"""
+
+        when:
+        def diagnostics = new DaemonGreeter(registry).parseDaemonOutput(output, Mock(ExecResult))
+
+        then:
+        diagnostics.pid == 12
+        diagnostics.daemonLog == new File("12.log")
+    }
+
+    def "shouts if daemon did not start"() {
+        given:
+        def output = """hey joe!
+another line of output..."""
+
+        ExecResult result = Mock()
+
+        when:
+        new DaemonGreeter(registry).parseDaemonOutput(output, result);
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains(DaemonMessages.UNABLE_TO_START_DAEMON)
+        ex.message.contains("hey joe!")
+    }
+
+    def "shouts if daemon broke completely..."() {
+        when:
+        new DaemonGreeter(registry).parseDaemonOutput("", Mock(ExecResult))
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains(DaemonMessages.UNABLE_TO_START_DAEMON)
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumerTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumerTest.groovy
new file mode 100644
index 0000000..15beb53
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonOutputConsumerTest.groovy
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.bootstrap
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 4/28/12
+ */
+class DaemonOutputConsumerTest extends Specification {
+
+    def consumer = new DaemonOutputConsumer()
+
+    def "input process and name cannot be null"() {
+        when:
+        consumer.connectStreams((Process) null, "foo")
+        then:
+        thrown(IllegalArgumentException)
+
+        when:
+        consumer.connectStreams(Mock(Process), null)
+        then:
+        thrown(IllegalArgumentException)
+    }
+
+    def "consumes input until EOF"() {
+        when:
+        consumer.connectStreams(new ByteArrayInputStream('hey Joe!'.bytes) , "cool process")
+        consumer.start()
+        consumer.stop()
+        then:
+        consumer.processOutput.trim() == 'hey Joe!'
+    }
+
+    def "consumes input greeting noticed in output"() {
+        given:
+        consumer.startupCommunication = Mock(DaemonStartupCommunication)
+        consumer.startupCommunication.containsGreeting( {it.contains "Come visit Krakow"} ) >> true
+
+        when:
+        def ouptut = """
+           Hey!
+           Come visit Krakow
+           It's nice
+           !!!
+        """
+        consumer.connectStreams(new ByteArrayInputStream(ouptut.toString().bytes) , "cool process")
+        consumer.start()
+        consumer.stop()
+
+        then:
+        consumer.processOutput.trim().endsWith("Come visit Krakow")
+    }
+
+    def "connecting streams is required initially"() {
+        expect:
+        illegalStateReportedWhen {consumer.start()}
+        illegalStateReportedWhen {consumer.stop()}
+        illegalStateReportedWhen {consumer.processOutput}
+    }
+
+    def "starting is required"() {
+        when:
+        consumer.connectStreams(new ByteArrayInputStream(new byte[0]) , "cool process")
+
+        then:
+        illegalStateReportedWhen {consumer.stop()}
+        illegalStateReportedWhen {consumer.processOutput}
+    }
+
+    def "stopping is required"() {
+        when:
+        consumer.connectStreams(new ByteArrayInputStream(new byte[0]) , "cool process")
+        consumer.start()
+
+        then:
+        illegalStateReportedWhen {consumer.processOutput}
+    }
+
+    void illegalStateReportedWhen(Closure action) {
+        try {
+            action()
+            assert false
+        } catch (IllegalStateException) {}
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunicationSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunicationSpec.groovy
new file mode 100644
index 0000000..c9a682a
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/bootstrap/DaemonStartupCommunicationSpec.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.bootstrap
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 4/10/12
+ */
+class DaemonStartupCommunicationSpec extends Specification {
+
+    def comm = new DaemonStartupCommunication()
+
+    def "can simply communicate diagnostics"() {
+        given:
+        def dummyFile = new File("C:\\foo;;\\daemon-123.log")
+
+        when:
+        def message = comm.daemonStartedMessage(123, dummyFile)
+        def diagnostics = comm.readDiagnostics(message)
+
+        then:
+        diagnostics.pid == 123
+        diagnostics.daemonLog == dummyFile
+    }
+
+    def "null pid is supported"() {
+        given:
+        def dummyFile = new File("C:\\foo;;\\daemon-123.log")
+
+        when:
+        def message = comm.daemonStartedMessage(null, dummyFile)
+        def diagnostics = comm.readDiagnostics(message)
+
+        then:
+        diagnostics.pid == null
+        diagnostics.daemonLog == dummyFile
+    }
+
+    def "knows if a message contains a greeting"() {
+        expect:
+        !comm.containsGreeting("foo")
+        comm.containsGreeting(comm.daemonStartedMessage(null, new File("foo")))
+
+        when:
+        comm.containsGreeting(null)
+        then:
+        thrown(IllegalArgumentException)
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientInputForwarderTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientInputForwarderTest.groovy
index 936d4eb..829c179 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientInputForwarderTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientInputForwarderTest.groovy
@@ -15,14 +15,16 @@
  */
 package org.gradle.launcher.daemon.client
 
-import java.util.concurrent.LinkedBlockingQueue
-import java.util.concurrent.TimeUnit
-import org.gradle.initialization.BuildClientMetaData
 import org.gradle.launcher.daemon.protocol.CloseInput
 import org.gradle.launcher.daemon.protocol.ForwardInput
 import org.gradle.messaging.dispatch.Dispatch
 import org.gradle.util.ConcurrentSpecification
+
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
+import org.gradle.internal.id.IdGenerator
 
 class DaemonClientInputForwarderTest extends ConcurrentSpecification {
 
@@ -52,7 +54,7 @@ class DaemonClientInputForwarderTest extends ConcurrentSpecification {
     def forwarder
 
     def createForwarder() {
-        forwarder = new DaemonClientInputForwarder(inputStream, [:] as BuildClientMetaData, dispatch, executorFactory, bufferSize)
+        forwarder = new DaemonClientInputForwarder(inputStream, dispatch, executorFactory, {12} as IdGenerator, bufferSize)
         forwarder.start()
     }
     
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientTest.groovy
index 6c05271..a9bc431 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DaemonClientTest.groovy
@@ -15,24 +15,23 @@
  */
 package org.gradle.launcher.daemon.client
 
-import org.gradle.api.specs.Spec
-import org.gradle.initialization.BuildClientMetaData
 import org.gradle.initialization.GradleLauncherAction
-import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.internal.id.IdGenerator
+import org.gradle.launcher.daemon.context.DaemonCompatibilitySpec
 import org.gradle.launcher.exec.BuildActionParameters
 import org.gradle.logging.internal.OutputEventListener
 import org.gradle.messaging.remote.internal.Connection
-import spock.lang.Specification
+import org.gradle.util.ConcurrentSpecification
 import org.gradle.launcher.daemon.protocol.*
 
-class DaemonClientTest extends Specification {
+class DaemonClientTest extends ConcurrentSpecification {
     final DaemonConnector connector = Mock()
     final DaemonConnection daemonConnection = Mock()
     final Connection<Object> connection = Mock()
-    final BuildClientMetaData metaData = Mock()
     final OutputEventListener outputEventListener = Mock()
-    final Spec<DaemonContext> compatibilitySpec = Mock()
-    final DaemonClient client = new DaemonClient(connector, metaData, outputEventListener, compatibilitySpec, System.in)
+    final DaemonCompatibilitySpec compatibilitySpec = Mock()
+    final IdGenerator<?> idGenerator = {12} as IdGenerator
+    final DaemonClient client = new DaemonClient(connector, outputEventListener, compatibilitySpec, new ByteArrayInputStream(new byte[0]), executorFactory, idGenerator)
 
     def setup() {
         daemonConnection.getConnection() >> connection
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy
index bcc7e0f..1db55c6 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/DefaultDaemonConnectorTest.groovy
@@ -15,9 +15,12 @@
  */
 package org.gradle.launcher.daemon.client
 
-import org.gradle.api.specs.Spec
+import org.gradle.api.GradleException
+import org.gradle.api.internal.specs.ExplainingSpec
+import org.gradle.api.internal.specs.ExplainingSpecs
 import org.gradle.launcher.daemon.context.DaemonContext
 import org.gradle.launcher.daemon.context.DefaultDaemonContext
+import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo
 import org.gradle.launcher.daemon.registry.EmbeddedDaemonRegistry
 import org.gradle.messaging.remote.Address
 import org.gradle.messaging.remote.internal.Connection
@@ -65,7 +68,7 @@ class DefaultDaemonConnectorTest extends Specification {
         def address = createAddress(daemonNum)
         registry.store(address, context, "password")
         registry.markBusy(address)
-        return daemonNum.toString()
+        return new DaemonStartupInfo(daemonNum.toString(), null);
     }
 
     def startIdleDaemon() {
@@ -92,13 +95,19 @@ class DefaultDaemonConnectorTest extends Specification {
         registry.all.size()
     }
 
+    abstract static class DummyExplainingSpec implements ExplainingSpec {
+        String whyUnsatisfied(Object element) {
+            ""
+        }
+    }
+
     def "maybeConnect() returns connection to any daemon that matches spec"() {
         given:
         startIdleDaemon()
         startIdleDaemon()
         
         expect:
-        def connection = connector.maybeConnect({it.pid < 12} as Spec)
+        def connection = connector.maybeConnect({it.pid < 12} as ExplainingSpec)
         connection && connection.connection.num < 12
     }
 
@@ -108,7 +117,7 @@ class DefaultDaemonConnectorTest extends Specification {
         startIdleDaemon()
 
         expect:
-        connector.maybeConnect({it.pid == 12} as Spec) == null
+        connector.maybeConnect({it.pid == 12} as DummyExplainingSpec) == null
     }
 
     def "maybeConnect() ignores daemons that do not match spec"() {
@@ -117,7 +126,7 @@ class DefaultDaemonConnectorTest extends Specification {
         startIdleDaemon()
 
         expect:
-        def connection = connector.maybeConnect({it.pid == 1} as Spec)
+        def connection = connector.maybeConnect({it.pid == 1} as DummyExplainingSpec)
         connection && connection.connection.num == 1
     }
 
@@ -127,7 +136,7 @@ class DefaultDaemonConnectorTest extends Specification {
         startIdleDaemon()
 
         expect:
-        def connection = connector.connect({it.pid < 12} as Spec)
+        def connection = connector.connect({it.pid < 12} as ExplainingSpec)
         connection && connection.connection.num < 12
 
         and:
@@ -139,7 +148,7 @@ class DefaultDaemonConnectorTest extends Specification {
         startIdleDaemon()
 
         expect:
-        def connection = connector.connect({it.pid > 0} as Spec)
+        def connection = connector.connect({it.pid > 0} as DummyExplainingSpec)
         connection && connection.connection.num > 0
 
         and:
@@ -151,21 +160,18 @@ class DefaultDaemonConnectorTest extends Specification {
         startIdleDaemon()
 
         expect:
-        def connection = connector.connect({it.pid != 0} as Spec)
+        def connection = connector.connect({it.pid != 0} as DummyExplainingSpec)
         connection && connection.connection.num != 0
 
         and:
         numAllDaemons == 2
     }
 
-    def "connect() will use daemon started by connector even if it fails compatibility spec"() {
+    def "connect() will fail early if newly started daemon fails the compatibility spec"() {
         when:
-        def connection = connector.connect({false} as Spec)
-        connection && connection.connection.num == 0
+        connector.connect(ExplainingSpecs.satisfyNone())
 
         then:
-        numAllDaemons == 1
+        thrown(GradleException)
     }
-
-
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/InputForwarderTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/InputForwarderTest.groovy
new file mode 100644
index 0000000..7255863
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/InputForwarderTest.groovy
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2011 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 org.gradle.launcher.daemon.client
+
+import org.gradle.api.Action
+import org.gradle.internal.concurrent.DefaultExecutorFactory
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+import static org.gradle.util.TextUtil.*
+
+import spock.lang.*
+import spock.util.concurrent.BlockingVariable
+
+class InputForwarderTest extends Specification {
+
+    def bufferSize = 1024
+    def executerFactory = new DefaultExecutorFactory()
+
+    def source = new PipedOutputStream()
+    def inputStream = new PipedInputStream(source)
+
+    def received = new LinkedBlockingQueue()
+    def finishedHolder = new BlockingVariable(2)
+
+    def action = action { received << it }
+    def onFinish = finished { finishedHolder.set(true) }
+
+    def action(Closure action) {
+        this.action = action as Action
+    }
+
+    def finished(Closure runnable) {
+        this.onFinish = runnable
+    }
+
+    def forwarder
+
+    def createForwarder() {
+        forwarder = new InputForwarder(inputStream, action, onFinish, executerFactory, bufferSize)
+        forwarder.start()
+    }
+
+    def receive(receive) {
+        assert received.poll(5, TimeUnit.SECONDS) == toPlatformLineSeparators(receive)
+        true
+    }
+
+    void input(str) {
+        source << toPlatformLineSeparators(str)
+    }
+    
+    boolean isFinished() {
+        finishedHolder.get() == true
+    }
+
+    boolean isNoMoreInput() {
+        receive null
+    }
+
+    def setup() {
+        createForwarder()
+    }
+
+    def closeInput() {
+        inputStream.close()
+        source.close()
+    }
+
+    def waitForForwarderToCollect() {
+        sleep 1000
+    }
+
+    def "input from source is forwarded until forwarder is stopped"() {
+        when:
+        input "abc\ndef\njkl"
+        waitForForwarderToCollect()
+        forwarder.stop()
+
+        then:
+        receive "abc\n"
+        receive "def\n"
+        receive "jkl"
+        noMoreInput
+
+        and:
+        finished
+    }
+
+    def "input from source is forwarded until source input stream is closed"() {
+        when:
+        input "abc\ndef\njkl"
+        waitForForwarderToCollect()
+        closeInput()
+
+        then:
+        receive "abc\n"
+        receive "def\n"
+        receive "jkl"
+        noMoreInput
+
+        and:
+        finished
+    }
+
+    def "output is buffered by line"() {
+        when:
+        input "a"
+
+        then:
+        noMoreInput
+
+        when:
+        input "b"
+
+        then:
+        noMoreInput
+
+        when:
+        input "\n"
+
+        then:
+        receive "ab\n"
+    }
+
+    def "one partial line when input stream closed gets forwarded"() {
+        when:
+        input "abc"
+        waitForForwarderToCollect()
+
+        and:
+        closeInput()
+
+        then:
+        receive "abc"
+
+        and:
+        noMoreInput
+    }
+
+    def "one partial line when forwarder stopped gets forwarded"() {
+        when:
+        input "abc"
+        waitForForwarderToCollect()
+
+        and:
+        forwarder.stop()
+
+        then:
+        receive "abc"
+
+        and:
+        noMoreInput
+    }
+
+    def "forwarder can be closed before receiving any output"() {
+        when:
+        forwarder.stop()
+
+        then:
+        noMoreInput
+    }
+
+    def "can handle lines larger than the buffer size"() {
+        given:
+        def longLine = "a" * (bufferSize * 10) + "\n"
+
+        when:
+        input longLine
+        input longLine
+
+        then:
+        receive longLine
+        receive longLine
+        noMoreInput
+    }
+
+    def cleanup() {
+        closeInput()
+        forwarder.stop()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/StopDispatcherTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/StopDispatcherTest.groovy
new file mode 100644
index 0000000..86e9412
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/client/StopDispatcherTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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 org.gradle.launcher.daemon.client
+
+import org.gradle.messaging.remote.internal.Connection
+import org.gradle.internal.id.IdGenerator
+import spock.lang.Specification
+
+/**
+ * @author: Szczepan Faber, created at: 9/13/11
+ */
+public class StopDispatcherTest extends Specification {
+
+    def dispatcher = new StopDispatcher({12} as IdGenerator)
+    def connection = Mock(Connection)
+
+    def "ignores failed dispatch and does not receive"() {
+        given:
+        connection.dispatch(_) >> { throw new RuntimeException("Cannot dispatch") }
+
+        when:
+        dispatcher.dispatch(connection)
+
+        then:
+        0 * connection.receive()
+        noExceptionThrown()
+    }
+
+    def "ignores failed receive"() {
+        given:
+        connection.receive() >> { throw new RuntimeException("Cannot dispatch") }
+
+        when:
+        dispatcher.dispatch(connection)
+
+        then:
+        1 * connection.dispatch(_)
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
index 7daef13..3aae06a 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/context/DaemonCompatibilitySpecSpec.groovy
@@ -16,8 +16,11 @@
 package org.gradle.launcher.daemon.context
 
 import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.filesystem.FileSystems
 import org.gradle.util.ConfigureUtil
+import org.gradle.util.Requires
 import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -50,13 +53,18 @@ class DaemonCompatibilitySpecSpec extends Specification {
         createContext(serverConfigure)
     }
 
-    boolean isCompatible() {
+    private boolean isCompatible() {
         new DaemonCompatibilitySpec(clientContext).isSatisfiedBy(serverContext)
     }
 
+    private String getUnsatisfiedReason() {
+        new DaemonCompatibilitySpec(clientContext).whyUnsatisfied(serverContext)
+    }
+
     def "default contexts are compatible"() {
         expect:
         compatible
+        !unsatisfiedReason
     }
 
     def "contexts with different java homes are incompatible"() {
@@ -65,6 +73,23 @@ class DaemonCompatibilitySpecSpec extends Specification {
 
         expect:
         !compatible
+        unsatisfiedReason.contains "Java home is different"
+    }
+
+    @Requires(TestPrecondition.SYMLINKS)
+    def "contexts with symlinked javaHome are compatible"() {
+        File dir = tmp.createDir("a")
+        File link = new File(tmp.dir, "link")
+        FileSystems.default.createSymbolicLink(link, dir)
+
+        assert dir != link
+        assert dir.canonicalFile == link.canonicalFile
+
+        client { javaHome = dir }
+        server { javaHome = link }
+
+        expect:
+        compatible
     }
 
     def "contexts with same daemon opts are compatible"() {
@@ -89,6 +114,7 @@ class DaemonCompatibilitySpecSpec extends Specification {
 
         expect:
         !compatible
+        unsatisfiedReason.contains "At least one daemon option is different"
     }
 
     def "contexts with different daemon opts are incompatible"() {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy
new file mode 100644
index 0000000..194da07
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/diagnostics/DaemonDiagnosticsTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.diagnostics
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 4/10/12
+ */
+class DaemonDiagnosticsTest extends Specification {
+
+    @Rule TemporaryFolder temp
+
+    def "tailing the daemon log is always safe"() {
+        given:
+        def diagnostics = new DaemonDiagnostics(new File("does not exist"), 123)
+
+        when:
+        diagnostics.describe()
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can describe itself"() {
+        given:
+        def log = temp.file("foo.log")
+        log << "hey joe!"
+        def diagnostics = new DaemonDiagnostics(log, 123)
+
+        when:
+        String desc = diagnostics.describe()
+
+        then:
+        desc.contains "123"
+        desc.contains log.name
+        desc.contains "hey joe!"
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy
index a42f247..c001742 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/DaemonRegistryServicesTest.groovy
@@ -15,6 +15,9 @@
  */
 package org.gradle.launcher.daemon.registry
 
+import org.gradle.launcher.daemon.context.DefaultDaemonContext
+import org.gradle.messaging.remote.internal.inet.SocketInetAddress
+import org.gradle.tests.fixtures.ConcurrentTestUtil
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
@@ -32,4 +35,20 @@ class DaemonRegistryServicesTest extends Specification {
         !registry("a").get(DaemonRegistry).is(registry("b").get(DaemonRegistry))
     }
     
+    @Rule ConcurrentTestUtil concurrent = new ConcurrentTestUtil()
+    
+    def "the registry can be concurrently written to"() {
+        when:
+        def registry = registry("someDir").createDaemonRegistry()
+        5.times { idx ->
+            concurrent.start {
+                def context = new DefaultDaemonContext("$idx", new File("$idx"), new File("$idx"), idx, 5000, [])
+                registry.store(new SocketInetAddress(new Inet6Address(), 8888 + idx), context, "foo-$idx")
+            }
+        }
+        concurrent.finished()
+
+        then:
+        registry.all.size() == 5
+    }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy
new file mode 100644
index 0000000..d243108
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/registry/PersistentDaemonRegistryTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.daemon.registry
+
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.launcher.daemon.context.DaemonContext
+import org.gradle.launcher.daemon.context.DaemonContextBuilder
+import org.gradle.messaging.remote.Address
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.createDefaultFileLockManager
+import static org.gradle.cache.internal.DefaultFileLockManagerTestHelper.unlockUncleanly
+
+class PersistentDaemonRegistryTest extends Specification {
+
+    @Rule TemporaryFolder tmp
+    
+    int addressCounter = 0
+
+    def "corrupt registry file is ignored"() {
+        given:
+        def file = tmp.file("registry")
+        def lockManager = createDefaultFileLockManager()
+        def registry = new PersistentDaemonRegistry(file, lockManager)
+        
+        and:
+        registry.store(address(), daemonContext(), "password")
+
+        expect:
+        registry.all.size() == 1
+
+        when:
+        unlockUncleanly(file)
+
+        then:
+        registry.all.empty
+    }
+    
+    DaemonContext daemonContext() {
+        new DaemonContextBuilder(new NativeServices().get(ProcessEnvironment)).with {
+            daemonRegistryDir = tmp.createDir("daemons")
+            create()
+        }
+    }
+    
+    Address address(int i = addressCounter++) {
+        new TestAddress(i.toString())
+    }
+
+    private static class TestAddress implements Address {
+
+        final String displayName
+
+        TestAddress(String displayName) {
+            this.displayName = displayName
+        }
+
+    }
+
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
index 844c446..fac9b84 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/DaemonServerExceptionHandlingTest.groovy
@@ -31,10 +31,11 @@ import org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter
 import org.gradle.launcher.daemon.server.exec.ForwardClientInput
 import org.gradle.launcher.exec.DefaultBuildActionParameters
 import org.gradle.logging.LoggingManagerInternal
-import org.gradle.messaging.concurrent.ExecutorFactory
+import org.gradle.internal.concurrent.ExecutorFactory
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.api.logging.LogLevel
 
 /**
  * by Szczepan Faber, created at: 12/21/11
@@ -42,7 +43,7 @@ import spock.lang.Specification
 class DaemonServerExceptionHandlingTest extends Specification {
 
     @Rule TemporaryFolder temp = new TemporaryFolder()
-    def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), 0, new HashMap(System.properties), [:], temp.dir)
+    def parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), 0, new HashMap(System.properties), [:], temp.dir, LogLevel.ERROR)
 
     static class DummyLauncherAction implements GradleLauncherAction, Serializable {
         Object someState
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/StopDispatcherTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/StopDispatcherTest.groovy
deleted file mode 100644
index 1d15c1b..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/StopDispatcherTest.groovy
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.launcher.daemon.server
-
-import org.gradle.initialization.BuildClientMetaData
-import org.gradle.messaging.remote.internal.Connection
-import spock.lang.Specification
-import org.gradle.launcher.daemon.client.StopDispatcher
-
-/**
- * @author: Szczepan Faber, created at: 9/13/11
- */
-public class StopDispatcherTest extends Specification {
-
-    def dispatcher = new StopDispatcher()
-    def connection = Mock(Connection)
-    def meta = Mock(BuildClientMetaData)
-
-    def "ignores failed dispatch and does not receive"() {
-        given:
-        connection.dispatch(_) >> { throw new RuntimeException("Cannot dispatch") }
-
-        when:
-        dispatcher.dispatch(meta, connection)
-
-        then:
-        0 * connection.receive()
-        noExceptionThrown()
-    }
-
-    def "ignores failed receive"() {
-        given:
-        connection.receive() >> { throw new RuntimeException("Cannot dispatch") }
-
-        when:
-        dispatcher.dispatch(meta, connection)
-
-        then:
-        1 * connection.dispatch(_)
-        noExceptionThrown()
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy
index 724a0e1..aba09df 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/DefaultBuildActionParametersTest.groovy
@@ -19,6 +19,7 @@ package org.gradle.launcher.exec;
 
 import org.gradle.configuration.GradleLauncherMetaData
 import spock.lang.Specification
+import org.gradle.api.logging.LogLevel
 
 /**
  * @author: Szczepan Faber, created at: 9/6/11
@@ -27,7 +28,7 @@ public class DefaultBuildActionParametersTest extends Specification {
 
     def "is serializable"() {
         given:
-        def params = new DefaultBuildActionParameters(new GradleLauncherMetaData(), System.currentTimeMillis(), System.properties, System.getenv(), new File("."))
+        def params = new DefaultBuildActionParameters(new GradleLauncherMetaData(), System.currentTimeMillis(), System.properties, System.getenv(), new File("."), LogLevel.ERROR)
         ObjectOutputStream out = new ObjectOutputStream(new ByteArrayOutputStream());
 
         when:
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/EntryPointTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/EntryPointTest.groovy
deleted file mode 100644
index 8058ff3..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/EntryPointTest.groovy
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.launcher.exec
-
-import spock.lang.Specification
-import org.gradle.api.Action
-
-class EntryPointTest extends Specification {
-    final Action<ExecutionListener> action = Mock()
-    final ProcessCompleter completer = Mock()
-    final EntryPoint entryPoint = new EntryPoint() {
-        @Override
-        protected ExecutionCompleter createCompleter() {
-            return completer
-        }
-
-        @Override
-        protected void doAction(ExecutionListener listener) {
-            action.execute(listener)
-        }
-    }
-
-    def "exits with success when action executes successfully"() {
-        when:
-        entryPoint.run()
-
-        then:
-        1 * action.execute(!null)
-        1 * completer.complete()
-        0 * _._
-    }
-
-    def "exits with failure when action reports a failure"() {
-        def failure = new RuntimeException()
-
-        when:
-        entryPoint.run()
-
-        then:
-        1 * action.execute(!null) >> { ExecutionListener listener -> listener.onFailure(failure) }
-        1 * completer.completeWithFailure(failure)
-        0 * _._
-    }
-
-    def "exits with failure when action throws exception"() {
-        def failure = new RuntimeException()
-
-        when:
-        entryPoint.run()
-
-        then:
-        1 * action.execute(!null) >> { throw failure }
-        1 * completer.completeWithFailure(failure)
-        0 * _._
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ExceptionReportingActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ExceptionReportingActionTest.groovy
deleted file mode 100644
index 7229ac3..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/ExceptionReportingActionTest.groovy
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.launcher.exec
-
-import org.gradle.api.Action
-import spock.lang.Specification
-
-class ExceptionReportingActionTest extends Specification {
-    final Action<ExecutionListener> target = Mock()
-    final ExecutionListener listener = Mock()
-    final Action<Throwable> reporter = Mock()
-    final ExceptionReportingAction action = new ExceptionReportingAction(target, reporter)
-
-    def executesAction() {
-        when:
-        action.execute(listener)
-
-        then:
-        1 * target.execute(listener)
-        0 * _._
-    }
-
-    def reportsExceptionThrownByAction() {
-        def failure = new RuntimeException()
-
-        when:
-        action.execute(listener)
-
-        then:
-        1 * target.execute(listener) >> { throw failure }
-        1 * reporter.execute(failure)
-        1 * listener.onFailure(failure)
-        0 * _._
-    }
-
-    def doesNotReportAlreadyReportedExceptionThrownByAction() {
-        def cause = new RuntimeException()
-        def failure = new ReportedException(cause)
-
-        when:
-        action.execute(listener)
-
-        then:
-        1 * target.execute(listener) >> { throw failure }
-        1 * listener.onFailure(cause)
-        0 * _._
-    }
-}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuterTest.groovy
new file mode 100644
index 0000000..3193438
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/exec/InProcessGradleLauncherActionExecuterTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012 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 org.gradle.launcher.exec
+
+import spock.lang.Specification
+import org.gradle.initialization.GradleLauncherFactory
+import org.gradle.initialization.GradleLauncherAction
+
+import org.gradle.initialization.BuildRequestMetaData
+import org.gradle.GradleLauncher
+import org.gradle.BuildResult
+
+import org.gradle.StartParameter
+
+class InProcessGradleLauncherActionExecuterTest extends Specification {
+    final GradleLauncherFactory factory = Mock()
+    final GradleLauncher launcher = Mock()
+    final BuildActionParameters param = Mock()
+    final BuildRequestMetaData metaData = Mock()
+    final BuildResult buildResult = Mock()
+    final InProcessGradleLauncherActionExecuter executer = new InProcessGradleLauncherActionExecuter(factory)
+
+    def setup() {
+        _ * param.buildRequestMetaData >> metaData
+    }
+
+    def "executes the provided action using a default StartParameter"() {
+        GradleLauncherAction<String> action = Mock()
+
+        given:
+        buildResult.failure >> null
+        action.result >> '<result>'
+
+        when:
+        def result = executer.execute(action, param)
+
+        then:
+        result == '<result>'
+
+        and:
+        1 * factory.newInstance(!null, metaData) >> launcher
+        1 * action.run(launcher) >> buildResult
+    }
+
+    def "executes the provided action using the provided StartParameter"() {
+        TestAction action = Mock()
+        def startParam = new StartParameter()
+
+        given:
+        action.configureStartParameter() >> startParam
+        buildResult.failure >> null
+        action.result >> '<result>'
+
+        when:
+        def result = executer.execute(action, param)
+
+        then:
+        result == '<result>'
+
+        and:
+        1 * factory.newInstance(startParam, metaData) >> launcher
+        1 * action.run(launcher) >> buildResult
+    }
+
+    def "wraps build failure"() {
+        def failure = new RuntimeException()
+        GradleLauncherAction<String> action = Mock()
+
+        given:
+        buildResult.failure >> failure
+
+        when:
+        executer.execute(action, param)
+
+        then:
+        ReportedException e = thrown()
+        e.cause == failure
+
+        and:
+        1 * factory.newInstance(!null, metaData) >> launcher
+        1 * action.run(launcher) >> buildResult
+    }
+}
+
+interface TestAction extends GradleLauncherAction<String>, InitializationAware {
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuterTest.groovy
deleted file mode 100644
index d4c1c4f..0000000
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/EmbeddedGradleLauncherActionExecuterTest.groovy
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.tooling.internal.provider
-
-import org.gradle.BuildResult
-import org.gradle.GradleLauncher
-import org.gradle.initialization.GradleLauncherAction
-import org.gradle.initialization.GradleLauncherFactory
-import org.gradle.launcher.exec.InitializationAware
-import org.gradle.tooling.internal.protocol.BuildExceptionVersion1
-import org.gradle.tooling.internal.provider.input.ProviderOperationParameters
-import spock.lang.Specification
-import org.gradle.StartParameter
-
-class EmbeddedGradleLauncherActionExecuterTest extends Specification {
-    final ProviderOperationParameters parameters = Mock()
-    final GradleLauncherFactory gradleLauncherFactory = Mock()
-    final GradleLauncher gradleLauncher = Mock()
-    final BuildResult buildResult = Mock()
-    final EmbeddedGradleLauncherActionExecuter executer = new EmbeddedGradleLauncherActionExecuter(gradleLauncherFactory)
-
-    def executesActionAndReturnsResult() {
-        GradleLauncherAction<String> action = Mock()
-
-        when:
-        def result = executer.execute(action, parameters)
-
-        then:
-        result == 'result'
-        1 * gradleLauncherFactory.newInstance(!null) >> gradleLauncher
-        1 * action.run(gradleLauncher) >> buildResult
-        1 * action.result >> 'result'
-    }
-
-    def actionCanConfigureStartParameters() {
-        TestAction action = Mock()
-        def StartParameter startParameter = new StartParameter()
-
-        when:
-        def result = executer.execute(action, parameters)
-
-        then:
-        result == 'result'
-        1 * action.configureStartParameter() >> startParameter
-        1 * gradleLauncherFactory.newInstance(startParameter) >> gradleLauncher
-        1 * action.run(gradleLauncher) >> buildResult
-        1 * action.result >> 'result'
-    }
-
-    def wrapsBuildFailure() {
-        GradleLauncherAction<String> action = Mock()
-        RuntimeException failure = new RuntimeException()
-
-        when:
-        executer.execute(action, parameters)
-
-        then:
-        BuildExceptionVersion1 e = thrown()
-        e.cause == failure
-        1 * gradleLauncherFactory.newInstance(!null) >> gradleLauncher
-        1 * action.run(gradleLauncher) >> buildResult
-        buildResult.failure >> failure
-    }
-}
-
-interface TestAction extends GradleLauncherAction<String>, InitializationAware {}
-
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy
index e26ba45..5e16888 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/LoggingBridgingGradleLauncherActionExecuterTest.groovy
@@ -63,9 +63,9 @@ class LoggingBridgingGradleLauncherActionExecuterTest extends Specification {
     def "sets log level accordingly"() {
         given:
         loggingManagerFactory.create() >> loggingManager
+        parameters.getBuildLogLevel() >> LogLevel.QUIET
 
         when:
-        parameters.getBuildLogLevel() >> LogLevel.QUIET
         executer.execute(action, parameters)
         
         then:
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
index 1c7e262..eaf5822 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
@@ -180,6 +180,7 @@ public abstract class AbstractMavenResolver implements MavenResolver, Dependency
         Set<MavenDeployment> mavenDeployments = getArtifactPomContainer().createDeployableFilesInfos();
         mavenSettingsSupplier.supply(installDeployTaskSupport);
         for (MavenDeployment mavenDeployment : mavenDeployments) {
+            ((CustomInstallDeployTaskSupport) installDeployTaskSupport).clearAttachedArtifactsList();
             beforeDeploymentActions.execute(mavenDeployment);
             addPomAndArtifact(installDeployTaskSupport, mavenDeployment);
             execute(installDeployTaskSupport);
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
index 78164ac..bd9499d 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
@@ -20,8 +20,7 @@ import org.apache.maven.settings.Settings;
 import org.codehaus.plexus.PlexusContainer;
 
 /**
- * We could also use reflection to get hold of the container property. But this would make it harder
- * to use a Mock for this class.
+ * We could also use reflection to get hold of the container property. But this would make it harder to use a Mock for this class.
  *
  * @author Hans Dockter
  */
@@ -30,7 +29,7 @@ public class CustomDeployTask extends DeployTask implements CustomInstallDeployT
     public synchronized Settings getSettings() {
         return super.getSettings();
     }
-    
+
     @Override
     public synchronized PlexusContainer getContainer() {
         return super.getContainer();
@@ -41,4 +40,8 @@ public class CustomDeployTask extends DeployTask implements CustomInstallDeployT
         LoggingHelper.injectLogger(getContainer(), getProject());
         super.doExecute();
     }
+
+    public void clearAttachedArtifactsList() {
+        attachedArtifacts.clear();
+    }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java
index 072bd4f..afd72d8 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallDeployTaskSupport.java
@@ -26,4 +26,5 @@ public interface CustomInstallDeployTaskSupport {
     Settings getSettings();
     Project getProject();
     AttachedArtifact createAttach();
+    void clearAttachedArtifactsList();
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java
index c3c2bef..9a87b04 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomInstallTask.java
@@ -27,6 +27,10 @@ public class CustomInstallTask extends InstallTask implements CustomInstallDeplo
         return super.getSettings();   
     }
 
+    public void clearAttachedArtifactsList() {
+        attachedArtifacts.clear();
+    }
+
     @Override
     public void doExecute() {
         LoggingHelper.injectLogger(getContainer(), getProject());
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
index 6025fe7..006a990 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
@@ -18,10 +18,7 @@ package org.gradle.api.publication.maven.internal.ant;
 import org.apache.maven.model.Dependency;
 import org.apache.maven.model.Exclusion;
 import org.gradle.api.GradleException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.DependencyArtifact;
-import org.gradle.api.artifacts.ExcludeRule;
-import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.*;
 import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
 import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
 import org.gradle.api.publication.maven.internal.ExcludeRuleConverter;
@@ -118,7 +115,12 @@ public class DefaultPomDependenciesConverter implements PomDependenciesConverter
             Set<Configuration> configurations) {
         Dependency mavenDependency =  new Dependency();
         mavenDependency.setGroupId(dependency.getGroup());
-        mavenDependency.setArtifactId(name);
+        if (dependency instanceof ProjectDependency) {
+            String artifactId = new ProjectDependencyArtifactIdExtractorHack((ProjectDependency) dependency).extract();
+            mavenDependency.setArtifactId(artifactId);
+        } else {
+            mavenDependency.setArtifactId(name);
+        }
         mavenDependency.setVersion(dependency.getVersion());
         mavenDependency.setType(type);
         mavenDependency.setScope(scope);
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHack.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHack.java
new file mode 100644
index 0000000..9e89767
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHack.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012 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 org.gradle.api.publication.maven.internal.ant;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.Nullable;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.artifacts.maven.MavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.api.plugins.BasePluginConvention;
+import org.gradle.api.tasks.Upload;
+import org.testng.internal.annotations.Sets;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Given a project dependency, determines the artifact ID that the depended-on project
+ * can be referred to from a Maven POM. Falls back to project.name if the artifact ID
+ * used for publishing the depended-on project cannot be determined with certainty.
+ * 
+ * The main goal of this class is to fix GRADLE-443 without changing any other existing
+ * behavior (e.g. when a project that gets published to a Maven repo depends on a
+ * project published to an Ivy repo).
+ *
+ * This class should be removed as soon as we have proper support for publications.
+ */
+public class ProjectDependencyArtifactIdExtractorHack {
+    private final Project project;
+
+    public ProjectDependencyArtifactIdExtractorHack(ProjectDependency dependency) {
+        this.project = dependency.getDependencyProject();
+    }
+
+    public String extract() {
+        Collection<Upload> tasks = project.getTasks().withType(Upload.class);
+        Collection<ArtifactRepository> repositories = getRepositories(tasks);
+        if (!onlyContainsMavenResolvers(repositories)) { return project.getName(); }
+
+        Collection<MavenDeployer> deployers = getMavenDeployers(repositories);
+        Set<String> artifactIds = getArtifactIds(deployers);
+        if (artifactIds.size() == 1) {
+            String artifactId = artifactIds.iterator().next();
+            if (artifactId != null && !artifactId.equals("empty-project")) {
+                return artifactId;
+            }
+        }
+        String baseName = getArchivesBaseName();
+        return baseName != null ? baseName : project.getName();
+    }
+
+    private Collection<ArtifactRepository> getRepositories(Collection<Upload> tasks) {
+        Collection<ArtifactRepository> result = Lists.newArrayList();
+        for (Upload task : tasks) {
+            result.addAll(task.getRepositories());
+        }
+        return result;
+    }
+
+    private boolean onlyContainsMavenResolvers(Collection<ArtifactRepository> repositories) {
+        for (ArtifactRepository repository : repositories) {
+            if (!(repository instanceof MavenResolver)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private Collection<MavenDeployer> getMavenDeployers(Collection<ArtifactRepository> repositories) {
+        Collection<MavenDeployer> result = Lists.newArrayList();
+        for (ArtifactRepository repository : repositories) {
+            if (repository instanceof MavenDeployer) {
+                result.add((MavenDeployer) repository);
+            }
+        }
+        return result;
+    }
+
+    private Set<String> getArtifactIds(Collection<MavenDeployer> deployers) {
+        Set<String> result = Sets.newHashSet();
+        for (MavenDeployer deployer : deployers) {
+            result.add(deployer.getPom().getArtifactId());
+        }
+        return result;
+    }
+
+    @Nullable
+    private String getArchivesBaseName() {
+        BasePluginConvention convention = project.getConvention().findPlugin(BasePluginConvention.class);
+        return convention != null ? convention.getArchivesBaseName() : null;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilder.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilder.groovy
index 5a20a48..41e8639 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilder.groovy
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/modelbuilder/MavenPublicationBuilder.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.publication.maven.internal.modelbuilder
 
 import org.gradle.api.Project
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.plugins.JavaPlugin
 import org.gradle.api.publication.maven.MavenPublication
 import org.gradle.api.publication.maven.internal.model.DefaultMavenArtifact
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java
index 90efcbd..734c43b 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolverTest.java
@@ -109,6 +109,7 @@ public abstract class AbstractMavenResolverTest {
 
         context.checking(new Expectations() {
             {
+                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).clearAttachedArtifactsList();
                 allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getSettings();
                 will(returnValue(mavenSettingsMock));
                 allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getProject();
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisherTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisherTest.groovy
index 47cfc83..d10c967 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisherTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/DefaultMavenPublisherTest.groovy
@@ -18,14 +18,12 @@ package org.gradle.api.publication.maven.internal.ant
 
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository
 import org.gradle.api.internal.file.DefaultTemporaryFileProvider
-import org.gradle.api.internal.file.FileSource
 import org.gradle.api.publication.maven.internal.model.DefaultMavenArtifact
 import org.gradle.api.publication.maven.internal.model.DefaultMavenPublication
 import org.gradle.util.Resources
 import org.gradle.util.TemporaryFolder
 import org.gradle.util.TestFile
 import org.junit.Rule
-
 import spock.lang.Specification
 
 /**
@@ -35,7 +33,7 @@ class DefaultMavenPublisherTest extends Specification {
     @Rule TemporaryFolder dir = new TemporaryFolder()
     @Rule Resources resources = new Resources()
 
-    def publisher = new DefaultMavenPublisher(dir.file("local-repository"), new DefaultTemporaryFileProvider({dir.createDir("tmp")} as FileSource))
+    def publisher = new DefaultMavenPublisher(dir.file("local-repository"), new DefaultTemporaryFileProvider({dir.createDir("tmp")} as org.gradle.internal.Factory))
     
     def "installs artifact"() {
         def publication = new DefaultMavenPublication(groupId: "gradleware.test", artifactId: "fooArtifact", version: "1.1")
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy
new file mode 100644
index 0000000..33570c3
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 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 org.gradle.api.publication.maven.internal.ant
+
+import org.gradle.util.HelperUtil
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency
+import org.gradle.api.plugins.BasePlugin
+import org.gradle.api.plugins.MavenPlugin
+
+import spock.lang.Specification
+import spock.lang.Issue
+
+ at Issue("GRADLE-443")
+class ProjectDependencyArtifactIdExtractorHackTest extends Specification {
+    def project = HelperUtil.createRootProject()
+    def extractor = new ProjectDependencyArtifactIdExtractorHack(new DefaultProjectDependency(project, null))
+
+    def "artifact ID defaults to project name if neither archivesBaseName nor mavenDeployer.pom.artifactId is configured"() {
+        expect:
+        extractor.extract() == project.name
+    }
+
+    def "artifact ID honors archivesBaseName"() {
+        project.plugins.apply(BasePlugin)
+        project.archivesBaseName = "changed"
+
+        expect:
+        extractor.extract() == "changed"
+    }
+
+    def "artifact ID honors mavenDeployer.pom.artifactId over archivesBaseName"() {
+        project.plugins.apply(MavenPlugin)
+
+        project.archivesBaseName = "changed"
+        project.uploadArchives {
+            repositories.mavenDeployer {
+                pom.artifactId = "changed2"
+            }
+        }
+
+        expect:
+        extractor.extract() == "changed2"
+    }
+
+    def "artifact ID defaults to project name if Ivy repository is configured"() {
+        project.plugins.apply(BasePlugin)
+        project.archivesBaseName = "changed"
+
+        project.uploadArchives {
+            repositories {
+                ivy {}
+            }
+        }
+
+        expect:
+        extractor.extract() == project.name
+    }
+
+    def "artifact ID defaults to project name if different mavenDeployer.pom.artifactId's are configured"() {
+        project.plugins.apply(MavenPlugin)
+
+        project.configurations { other }
+        project.uploadArchives {
+            repositories.mavenDeployer {
+                pom.artifactId = "changed"
+            }
+        }
+        project.uploadOther {
+            repositories.mavenDeployer {
+                pom.artifactId = "changed2"
+            }
+        }
+
+        expect:
+        extractor.extract() == project.name
+    }
+}
diff --git a/subprojects/messaging/messaging.gradle b/subprojects/messaging/messaging.gradle
new file mode 100644
index 0000000..7ab1617
--- /dev/null
+++ b/subprojects/messaging/messaging.gradle
@@ -0,0 +1,8 @@
+dependencies {
+    groovy libraries.groovy
+    publishCompile project(':baseServices')
+    publishCompile libraries.slf4j_api
+    publishCompile libraries.guava
+}
+
+useTestFixtures()
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/actor/Actor.java b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/Actor.java
new file mode 100644
index 0000000..329830c
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/Actor.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.actor;
+
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.ThreadSafe;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.DispatchException;
+import org.gradle.messaging.dispatch.MethodInvocation;
+
+/**
+ * <p>An {@code Actor} dispatches method calls to a target object in a thread-safe manner. Methods are called either by
+ * calling {@link org.gradle.messaging.dispatch.Dispatch#dispatch(Object)} on the actor, or using the proxy object
+ * returned by {@link #getProxy(Class)}. Methods are delivered to the target object in the order they are called on the
+ * actor, but are delivered to the target object by a single thread at a time. In this way, the target object does not need
+ * to perform any synchronisation.</p>
+ *
+ * <p>An actor uses one of two modes to deliver method calls to the target object:</p>
+ *
+ * <ul>
+ * <li>Non-blocking, or asynchronous, so that method dispatch does not block waiting for the method call to be delivered or executed.
+ * In this mode, the method return value or exception is not delivered back to the dispatcher.
+ * </li>
+ *
+ * <li>Blocking, or synchronous, so that method dispatch blocks until the method call has been delivered and executed. In this mode, the
+ * method return value or exception is delivered back to the dispatcher.
+ * </li>
+ *
+ * </ul>
+ *
+ * <p>All implementations of this interface must be thread-safe.</p>
+ */
+public interface Actor extends Dispatch<MethodInvocation>, Stoppable, ThreadSafe {
+    /**
+     * Creates a proxy which delivers method calls to the target object.
+     *
+     * @param type the type for the proxy.
+     * @return The proxy.
+     */
+    <T> T getProxy(Class<T> type);
+
+    /**
+     * Stops accepting new method calls, and blocks until all method calls have been executed by the target object.
+     *
+     * @throws DispatchException When there were any failures dispatching method calls to the target object.
+     */
+    void stop() throws DispatchException;
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.java b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/ActorFactory.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/actor/ActorFactory.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/actor/internal/DefaultActorFactory.java b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/internal/DefaultActorFactory.java
new file mode 100644
index 0000000..e3bfd83
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/actor/internal/DefaultActorFactory.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.actor.internal;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.internal.concurrent.ThreadSafe;
+import org.gradle.messaging.actor.Actor;
+import org.gradle.messaging.actor.ActorFactory;
+import org.gradle.messaging.dispatch.*;
+import org.slf4j.LoggerFactory;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * A basic {@link ActorFactory} implementation. Currently cannot support creating both a blocking and non-blocking actor for the same target object.
+ */
+public class DefaultActorFactory implements ActorFactory, Stoppable {
+    private final Map<Object, NonBlockingActor> nonBlockingActors = new IdentityHashMap<Object, NonBlockingActor>();
+    private final Map<Object, BlockingActor> blockingActors = new IdentityHashMap<Object, BlockingActor>();
+    private final Object lock = new Object();
+    private final ExecutorFactory executorFactory;
+
+    public DefaultActorFactory(ExecutorFactory executorFactory) {
+        this.executorFactory = executorFactory;
+    }
+
+    /**
+     * Stops all actors.
+     */
+    public void stop() {
+        synchronized (lock) {
+            try {
+                new CompositeStoppable().add(nonBlockingActors.values()).add(blockingActors.values()).stop();
+            } finally {
+                nonBlockingActors.clear();
+            }
+        }
+    }
+
+    public Actor createActor(Object target) {
+        if (target instanceof NonBlockingActor) {
+            return (NonBlockingActor) target;
+        }
+        synchronized (lock) {
+            if (blockingActors.containsKey(target)) {
+                throw new UnsupportedOperationException("Cannot create a non-blocking and blocking actor for the same object. This is not implemented yet.");
+            }
+            NonBlockingActor actor = nonBlockingActors.get(target);
+            if (actor == null) {
+                actor = new NonBlockingActor(target);
+                nonBlockingActors.put(target, actor);
+            }
+            return actor;
+        }
+    }
+
+    public Actor createBlockingActor(Object target) {
+        synchronized (lock) {
+            if (nonBlockingActors.containsKey(target)) {
+                throw new UnsupportedOperationException("Cannot create a non-blocking and blocking actor for the same object. This is not implemented yet.");
+            }
+            BlockingActor actor = blockingActors.get(target);
+            if (actor == null) {
+                actor = new BlockingActor(target);
+                blockingActors.put(target, actor);
+            }
+            return actor;
+        }
+    }
+
+    private void stopped(NonBlockingActor actor) {
+        synchronized (lock) {
+            nonBlockingActors.values().remove(actor);
+        }
+    }
+
+    private void stopped(BlockingActor actor) {
+        synchronized (lock) {
+            blockingActors.values().remove(actor);
+        }
+    }
+
+    private class BlockingActor implements Actor {
+        private final Dispatch<MethodInvocation> dispatch;
+        private final Object lock = new Object();
+        private boolean stopped;
+
+        public BlockingActor(Object target) {
+            dispatch = new ReflectionDispatch(target);
+        }
+
+        public <T> T getProxy(Class<T> type) {
+            return new ProxyDispatchAdapter<T>(this, type, ThreadSafe.class).getSource();
+        }
+
+        public void stop() throws DispatchException {
+            synchronized (lock) {
+                stopped = true;
+            }
+            stopped(this);
+        }
+
+        public void dispatch(MethodInvocation message) {
+            synchronized (lock) {
+                if (stopped) {
+                    throw new IllegalStateException("This actor has been stopped.");
+                }
+                dispatch.dispatch(message);
+            }
+        }
+    }
+
+    private class NonBlockingActor implements Actor {
+        private final Dispatch<MethodInvocation> dispatch;
+        private final StoppableExecutor executor;
+        private final ExceptionTrackingFailureHandler failureHandler;
+
+        public NonBlockingActor(Object targetObject) {
+            executor = executorFactory.create(String.format("Dispatch %s", targetObject));
+            failureHandler = new ExceptionTrackingFailureHandler(LoggerFactory.getLogger(NonBlockingActor.class));
+            dispatch = new AsyncDispatch<MethodInvocation>(executor,
+                    new FailureHandlingDispatch<MethodInvocation>(
+                            new ReflectionDispatch(targetObject),
+                            failureHandler));
+        }
+
+        public <T> T getProxy(Class<T> type) {
+            return new ProxyDispatchAdapter<T>(this, type, ThreadSafe.class).getSource();
+        }
+
+        public void stop() {
+            try {
+                new CompositeStoppable(dispatch, executor, failureHandler).stop();
+            } finally {
+                stopped(this);
+            }
+        }
+
+        public void dispatch(MethodInvocation message) {
+            dispatch.dispatch(message);
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/AsyncDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/AsyncDispatch.java
new file mode 100755
index 0000000..fd45835
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/AsyncDispatch.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.dispatch;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.AsyncStoppable;
+
+import java.util.LinkedList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <p>A {@link org.gradle.messaging.dispatch.Dispatch} implementation which delivers messages asynchronously. Calls to
+ * {@link #dispatch} queue the message. Worker threads deliver the messages in the order they have been received to one
+ * of a pool of delegate {@link org.gradle.messaging.dispatch.Dispatch} instances.</p>
+ */
+public class AsyncDispatch<T> implements Dispatch<T>, AsyncStoppable {
+    private enum State {
+        Init, Stopped
+    }
+
+    private static final int MAX_QUEUE_SIZE = 200;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final LinkedList<T> queue = new LinkedList<T>();
+    private final Executor executor;
+    private final int maxQueueSize;
+    private int dispatchers;
+    private State state;
+
+    public AsyncDispatch(Executor executor) {
+        this(executor, null, MAX_QUEUE_SIZE);
+    }
+
+    public AsyncDispatch(Executor executor, final Dispatch<? super T> dispatch) {
+        this(executor, dispatch, MAX_QUEUE_SIZE);
+    }
+
+    public AsyncDispatch(Executor executor, final Dispatch<? super T> dispatch, int maxQueueSize) {
+        this.executor = executor;
+        this.maxQueueSize = maxQueueSize;
+        state = State.Init;
+        if (dispatch != null) {
+            dispatchTo(dispatch);
+        }
+    }
+
+    /**
+     * Starts dispatching messages to the given handler. The handler does not need to be thread-safe.
+     */
+    public void dispatchTo(final Dispatch<? super T> dispatch) {
+        onDispatchThreadStart();
+        executor.execute(new Runnable() {
+            public void run() {
+                try {
+                    dispatchMessages(dispatch);
+                } finally {
+                    onDispatchThreadExit();
+                }
+            }
+        });
+    }
+
+    private void onDispatchThreadStart() {
+        lock.lock();
+        try {
+            if (state != State.Init) {
+                throw new IllegalStateException("This dispatch has been stopped.");
+            }
+            dispatchers++;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onDispatchThreadExit() {
+        lock.lock();
+        try {
+            dispatchers--;
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void setState(State state) {
+        this.state = state;
+        condition.signalAll();
+    }
+
+    private void dispatchMessages(Dispatch<? super T> dispatch) {
+        while (true) {
+            T message = null;
+            lock.lock();
+            try {
+                while (state != State.Stopped && queue.isEmpty()) {
+                    try {
+                        condition.await();
+                    } catch (InterruptedException e) {
+                        throw new UncheckedException(e);
+                    }
+                }
+                if (!queue.isEmpty()) {
+                    message = queue.remove();
+                    condition.signalAll();
+                }
+            } finally {
+                lock.unlock();
+            }
+
+            if (message == null) {
+                // Have been stopped and nothing to deliver
+                return;
+            }
+
+            dispatch.dispatch(message);
+        }
+    }
+
+    public void dispatch(final T message) {
+        lock.lock();
+        try {
+            while (state != State.Stopped && queue.size() >= maxQueueSize) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw new UncheckedException(e);
+                }
+            }
+            if (state == State.Stopped) {
+                throw new IllegalStateException("Cannot dispatch message, as this message dispatch has been stopped. Message: " + message);
+            }
+            queue.add(message);
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Commences a shutdown of this dispatch.
+     */
+    public void requestStop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void doRequestStop() {
+        setState(State.Stopped);
+    }
+
+    /**
+     * Stops accepting new messages, and blocks until all queued messages have been dispatched.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            setState(State.Stopped);
+            while (dispatchers > 0) {
+                condition.await();
+            }
+
+            if (!queue.isEmpty()) {
+                throw new IllegalStateException(
+                        "Cannot wait for messages to be dispatched, as there are no dispatch threads running.");
+            }
+        } catch (InterruptedException e) {
+            throw new UncheckedException(e);
+        } finally {
+            lock.unlock();
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/AsyncReceive.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/AsyncReceive.java
new file mode 100644
index 0000000..41ad4d1
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/AsyncReceive.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.dispatch;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.AsyncStoppable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <p>Receives messages asynchronously. One or more {@link Receive} instances can use used as a source of messages. Messages are sent to a {@link Dispatch} </p>
+ * 
+ * <p>It is also possible to specify an <code>onReceiversExhausted</code> Runnable callback that will be run when all of the given receivers
+ * have been exhausted of messages. However, the current implementation is flawed in that this may erroneously fire if the first receiver
+ * is exhausted before the second starts.
+ */
+public class AsyncReceive<T> implements AsyncStoppable {
+    private enum State {
+        Init, Stopping, Stopped
+    }
+
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final Executor executor;
+    private final List<Dispatch<? super T>> dispatches = new ArrayList<Dispatch<? super T>>();
+    private final Runnable onReceiversExhausted;
+    private int receivers;
+    private State state = State.Init;
+
+    public AsyncReceive(Executor executor) {
+        this(executor, (Runnable)null);
+    }
+
+    public AsyncReceive(Executor executor, Runnable onReceiversExhausted) {
+        this.executor = executor;
+        this.onReceiversExhausted = onReceiversExhausted;
+    }
+
+    public AsyncReceive(Executor executor, final Dispatch<? super T> dispatch) {
+        this(executor, dispatch, null);
+    }
+
+    public AsyncReceive(Executor executor, final Dispatch<? super T> dispatch, Runnable onReceiversExhausted) {
+        this(executor, onReceiversExhausted);
+        dispatchTo(dispatch);
+    }
+
+    /**
+     * Starts dispatching to the given dispatch. The dispatch does not need be thread-safe.
+     */
+    public void dispatchTo(final Dispatch<? super T> dispatch) {
+        lock.lock();
+        try {
+            dispatches.add(dispatch);
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Starts receiving from the given receive. The receive does not need to be thread-safe.
+     */
+    public void receiveFrom(final Receive<? extends T> receive) {
+        onReceiveThreadStart();
+        executor.execute(new Runnable() {
+            public void run() {
+                try {
+                    receiveMessages(receive);
+                } finally {
+                    onReceiveThreadExit();
+                }
+            }
+        });
+    }
+
+    private void onReceiveThreadStart() {
+        lock.lock();
+        try {
+            if (state != State.Init) {
+                throw new IllegalStateException("This receiver has been stopped.");
+            }
+            receivers++;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onReceiveThreadExit() {
+        lock.lock();
+        try {
+            receivers--;
+            if (receivers == 0 && onReceiversExhausted != null) {
+                onReceiversExhausted.run();
+            }
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void receiveMessages(Receive<? extends T> receive) {
+        while (true) {
+            Dispatch<? super T> dispatch;
+            lock.lock();
+            try {
+                while (dispatches.isEmpty() && state == State.Init) {
+                    try {
+                        condition.await();
+                    } catch (InterruptedException e) {
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                }
+                if (state != State.Init) {
+                    return;
+                }
+                dispatch = dispatches.remove(0);
+            } finally {
+                lock.unlock();
+            }
+
+            try {
+                T message = receive.receive();
+                if (message == null) {
+                    return;
+                }
+
+                dispatch.dispatch(message);
+            } finally {
+                lock.lock();
+                try {
+                    dispatches.add(dispatch);
+                    condition.signalAll();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        }
+    }
+
+    private void setState(State state) {
+        this.state = state;
+        condition.signalAll();
+    }
+
+    /**
+     * Stops receiving new messages.
+     */
+    public void requestStop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void doRequestStop() {
+        if (receivers > 0) {
+            setState(State.Stopping);
+        } else {
+            setState(State.Stopped);
+        }
+    }
+
+    /**
+     * Stops receiving new messages. Blocks until all queued messages have been delivered.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            doRequestStop();
+
+            while (receivers > 0) {
+                condition.await();
+            }
+
+            setState(State.Stopped);
+        } catch (InterruptedException e) {
+            throw new UncheckedException(e);
+        } finally {
+            lock.unlock();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ContextClassLoaderDispatch.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatch.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ContextClassLoaderDispatch.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DelayedReceive.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DelayedReceive.java
new file mode 100644
index 0000000..21a8cc6
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DelayedReceive.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.dispatch;
+
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.UncheckedException;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.PriorityQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DelayedReceive<T> implements Stoppable, Receive<T> {
+    private final TimeProvider timeProvider;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final PriorityQueue<DelayedMessage> queue = new PriorityQueue<DelayedMessage>();
+    private boolean stopping;
+
+    public DelayedReceive(TimeProvider timeProvider) {
+        this.timeProvider = timeProvider;
+    }
+
+    public T receive() {
+        lock.lock();
+        try {
+            while (true) {
+                DelayedMessage message = queue.peek();
+                if (message == null && stopping) {
+                    return null;
+                }
+                if (message == null) {
+                    condition.await();
+                    continue;
+                }
+
+                long now = timeProvider.getCurrentTime();
+                if (message.dispatchTime > now) {
+                    condition.awaitUntil(new Date(message.dispatchTime));
+                } else {
+                    queue.poll();
+                    if (queue.isEmpty()) {
+                        condition.signalAll();
+                    }
+                    return message.message;
+                }
+            }
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Dispatches the given message after the given delay.
+     */
+    public void dispatchLater(T message, int delayValue, TimeUnit delayUnits) {
+        long dispatchTime = timeProvider.getCurrentTime() + delayUnits.toMillis(delayValue);
+        lock.lock();
+        try {
+            if (stopping) {
+                throw new IllegalStateException("This dispatch has been stopped.");
+            }
+            queue.add(new DelayedMessage(dispatchTime, message));
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Removes one instance of the given message from the queue.
+     *
+     * @return true if removed, false if not. If false is returned, the message may be currently being dispatched.
+     */
+    public boolean remove(T message) {
+        lock.lock();
+        try {
+            Iterator<DelayedMessage> iterator = queue.iterator();
+            while (iterator.hasNext()) {
+                DelayedMessage next = iterator.next();
+                if (next.message.equals(message)) {
+                    iterator.remove();
+                    return true;
+                }
+            }
+            return false;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Removes all queued messages.
+     */
+    public void clear() {
+        lock.lock();
+        try {
+            queue.clear();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Blocks until all queued messages are delivered.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            stopping = true;
+            condition.signalAll();
+            while (!queue.isEmpty()) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private class DelayedMessage implements Comparable<DelayedMessage> {
+        private final long dispatchTime;
+        private final T message;
+
+        private DelayedMessage(long dispatchTime, T message) {
+            this.dispatchTime = dispatchTime;
+            this.message = message;
+        }
+
+        public int compareTo(DelayedMessage delayedMessage) {
+            if (dispatchTime > delayedMessage.dispatchTime) {
+                return 1;
+            } else if (dispatchTime < delayedMessage.dispatchTime) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DiscardingFailureHandler.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DiscardingFailureHandler.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DiscardingFailureHandler.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DiscardingFailureHandler.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/Dispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/Dispatch.java
new file mode 100755
index 0000000..e5ec950
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/Dispatch.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.dispatch;
+
+/**
+ * A sink for messages. Implementations do not have to be thread-safe.
+ */
+public interface Dispatch<T> {
+    /**
+     * Dispatches the next message. Blocks until the messages has been accepted but generally does not wait for the
+     * message to be processed. Delivery guarantees are implementation specific.
+     *
+     * @param message The message.
+     */
+    void dispatch(T message);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DispatchException.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DispatchException.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DispatchException.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DispatchException.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DispatchFailureHandler.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DispatchFailureHandler.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/DispatchFailureHandler.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/DispatchFailureHandler.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandler.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/FailureHandlingDispatch.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatch.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/FailureHandlingDispatch.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/MethodInvocation.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/MethodInvocation.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/QueuingDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/QueuingDispatch.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/QueuingDispatch.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/QueuingDispatch.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/Receive.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/Receive.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ReflectionDispatch.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/dispatch/ReflectionDispatch.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/Address.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/Address.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/Address.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/Address.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/Addressable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/Addressable.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/Addressable.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/Addressable.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ConnectEvent.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/ConnectEvent.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingClient.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingClient.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/MessagingServer.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java
new file mode 100755
index 0000000..14c2857
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/ObjectConnection.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote;
+
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+
+/**
+ * Manages a set of incoming and outgoing channels between 2 peers. Implementations must be thread-safe.
+ */
+public interface ObjectConnection extends Addressable, AsyncStoppable {
+    /**
+     * Creates a transmitter for outgoing messages on the given type. The returned object is thread-safe.
+     *
+     * @param type The type
+     * @return A sink. Method calls made on this object are sent as outgoing messages.
+     */
+    <T> T addOutgoing(Class<T> type);
+
+    /**
+     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
+     * thread-safe. Messages are delivered to the handler by a single thread.
+     *
+     * @param type The type.
+     * @param instance The handler instance. Incoming messages on the given type are delivered to this handler.
+     */
+    <T> void addIncoming(Class<T> type, T instance);
+
+    /**
+     * Registers a handler for incoming messages on the given type. The provided handler is not required to be
+     * thread-safe. Messages are delivered to the handler by a single thread.
+     *
+     * @param type The type.
+     * @param dispatch The handler instance. Incoming messages on the given type are delivered to this handler.
+     */
+    void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch);
+
+    /**
+     * Commences a graceful stop of this connection. Stops accepting outgoing messages. Requests that the peer stop
+     * sending incoming messages.
+     */
+    void requestStop();
+
+    /**
+     * Performs a graceful stop of this connection. Stops accepting outgoing message. Blocks until all incoming messages
+     * have been handled, and all outgoing messages have been handled by the peer.
+     */
+    void stop();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnection.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/AsyncConnection.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnection.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
new file mode 100644
index 0000000..93aeb41
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/AsyncConnectionAdapter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Adapts a {@link Connection} into an {@link AsyncConnection}.
+ */
+public class AsyncConnectionAdapter<T> implements AsyncConnection<T>, Stoppable {
+    private final Connection<T> connection;
+    private final AsyncReceive<T> incoming;
+    private final ProtocolStack<T> stack;
+    private final AsyncDispatch<T> outgoing;
+    private final Set<Stoppable> executors = new HashSet<Stoppable>();
+
+    public AsyncConnectionAdapter(Connection<T> connection, DispatchFailureHandler<? super T> dispatchFailureHandler, ExecutorFactory executor, Protocol<T>... protocols) {
+        this.connection = connection;
+
+        StoppableExecutor outgoingExecutor = executor.create(String.format("%s send", connection));
+        executors.add(outgoingExecutor);
+        outgoing = new AsyncDispatch<T>(outgoingExecutor);
+        outgoing.dispatchTo(new FailureHandlingDispatch<T>(connection, dispatchFailureHandler));
+
+        StoppableExecutor dispatchExecutor = executor.create(String.format("%s dispatch", connection));
+        executors.add(dispatchExecutor);
+        stack = new ProtocolStack<T>(dispatchExecutor, dispatchFailureHandler, dispatchFailureHandler, protocols);
+        stack.getBottom().dispatchTo(outgoing);
+
+        StoppableExecutor incomingExecutor = executor.create(String.format("%s receive", connection));
+        executors.add(incomingExecutor);
+        incoming = new AsyncReceive<T>(incomingExecutor);
+        incoming.dispatchTo(stack.getBottom());
+        incoming.receiveFrom(new ConnectionReceive<T>(connection));
+    }
+
+    public void dispatch(T message) {
+        stack.getTop().dispatch(message);
+    }
+
+    public void dispatchTo(Dispatch<? super T> handler) {
+        stack.getTop().dispatchTo(handler);
+    }
+
+    public void stop() {
+        new CompositeStoppable(stack, outgoing, connection, incoming).add(executors).stop();
+    }
+
+    private class ConnectionReceive<T> implements Receive<T> {
+        private final Connection<T> connection;
+
+        public ConnectionReceive(Connection<T> connection) {
+            this.connection = connection;
+        }
+
+        public T receive() {
+            T result = connection.receive();
+            if (result == null) {
+                stack.requestStop();
+            }
+            return result;
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/BroadcastSendProtocol.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocol.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/BroadcastSendProtocol.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BufferingProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/BufferingProtocol.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/BufferingProtocol.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/BufferingProtocol.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ChannelLookupProtocol.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocol.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ChannelLookupProtocol.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ChannelRegistrationProtocol.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocol.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ChannelRegistrationProtocol.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/CompositeAddress.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/CompositeAddress.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/CompositeAddress.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/CompositeAddress.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ConnectException.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ConnectException.java
new file mode 100755
index 0000000..1c71855
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ConnectException.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+public class ConnectException extends RuntimeException {
+    public ConnectException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Connection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Connection.java
new file mode 100755
index 0000000..5e485ac
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Connection.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.Receive;
+
+/**
+ * <p>A messaging endpoint which allows push-style dispatch and pull-style receive.
+ *
+ * <p>Implementations are not guaranteed to be completely thread-safe.
+ * However, the implementations:
+ * <ul>
+ * <li>should allow separate threads for dispatching and receiving, i.e. single thread that dispatches
+ * and a different single thread that receives should be perfectly safe</li>
+ * <li>should allow stopping or requesting stopping from a different thread than receiving/dispatching</li>
+ * <li>don't guarantee allowing multiple threads dispatching</li>
+ * </li>
+ * </ul>
+ */
+public interface Connection<T> extends Dispatch<T>, Receive<T>, AsyncStoppable {
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
new file mode 100644
index 0000000..1d0ead8
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultIncomingBroadcast.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.api.Action;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.internal.protocol.ChannelAvailable;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.internal.id.IdGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DefaultIncomingBroadcast implements IncomingBroadcast, Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIncomingBroadcast.class);
+    private final ProtocolStack<DiscoveryMessage> protocolStack;
+    private final MessageOriginator messageOriginator;
+    private final String group;
+    private final Lock lock = new ReentrantLock();
+    private final Set<String> channels = new HashSet<String>();
+    private final StoppableExecutor executor;
+    private final Address address;
+    private final MessageHub hub;
+
+    public DefaultIncomingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, IncomingConnector<Message> incomingConnector, ExecutorFactory executorFactory, IdGenerator<UUID> idGenerator, ClassLoader messagingClassLoader) {
+        this.messageOriginator = messageOriginator;
+        this.group = group;
+
+        executor = executorFactory.create("discovery broadcast");
+        DiscardingFailureHandler<DiscoveryMessage> failureHandler = new DiscardingFailureHandler<DiscoveryMessage>(LOGGER);
+        protocolStack = new ProtocolStack<DiscoveryMessage>(executor, failureHandler, failureHandler, new ChannelRegistrationProtocol(messageOriginator));
+        connection.dispatchTo(new GroupMessageFilter(group, protocolStack.getBottom()));
+        protocolStack.getBottom().dispatchTo(connection);
+
+        address = incomingConnector.accept(new IncomingConnectionAction(), true);
+        hub = new MessageHub("incoming broadcast", messageOriginator.getName(), executorFactory, idGenerator, messagingClassLoader);
+
+        LOGGER.info("Created IncomingBroadcast with {}", messageOriginator);
+    }
+
+    public <T> void addIncoming(Class<T> type, T handler) {
+        String channelKey = type.getName();
+        lock.lock();
+        try {
+            if (channels.add(channelKey)) {
+                protocolStack.getTop().dispatch(new ChannelAvailable(messageOriginator, group, channelKey, address));
+            }
+        } finally {
+            lock.unlock();
+        }
+        hub.addIncoming(channelKey, new TypeCastDispatch<MethodInvocation, Object>(MethodInvocation.class, new ReflectionDispatch(handler)));
+    }
+
+    public void stop() {
+        new CompositeStoppable().add(protocolStack, hub, executor).stop();
+    }
+
+    private class IncomingConnectionAction implements Action<ConnectEvent<Connection<Message>>> {
+        public void execute(ConnectEvent<Connection<Message>> connectionConnectEvent) {
+            hub.addConnection(connectionConnectEvent.getConnection());
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessageSerializer.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
new file mode 100755
index 0000000..d45a989
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.api.Action;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.ObjectConnection;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class DefaultMessagingServer implements MessagingServer, Stoppable {
+    private final MultiChannelConnector connector;
+    private final ClassLoader classLoader;
+    private final Set<ObjectConnection> connections = new CopyOnWriteArraySet<ObjectConnection>();
+
+    public DefaultMessagingServer(MultiChannelConnector connector, ClassLoader classLoader) {
+        this.connector = connector;
+        this.classLoader = classLoader;
+    }
+
+    public Address accept(final Action<ConnectEvent<ObjectConnection>> action) {
+        return connector.accept(new Action<ConnectEvent<MultiChannelConnection<Object>>>() {
+            public void execute(ConnectEvent<MultiChannelConnection<Object>> connectEvent) {
+                finishConnect(connectEvent, action);
+            }
+        });
+    }
+
+    private void finishConnect(ConnectEvent<MultiChannelConnection<Object>> connectEvent,
+                               Action<ConnectEvent<ObjectConnection>> action) {
+        MultiChannelConnection<Object> messageConnection = connectEvent.getConnection();
+        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(messageConnection);
+        OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(messageConnection);
+        AtomicReference<ObjectConnection> connectionRef = new AtomicReference<ObjectConnection>();
+        AsyncStoppable stopControl = new ConnectionAsyncStoppable(messageConnection, connectionRef);
+
+        DefaultObjectConnection connection = new DefaultObjectConnection(messageConnection, stopControl, outgoing, incoming);
+        connectionRef.set(connection);
+        connections.add(connection);
+        action.execute(new ConnectEvent<ObjectConnection>(connection, connectEvent.getLocalAddress(), connectEvent.getRemoteAddress()));
+    }
+
+    public void stop() {
+        for (ObjectConnection connection : connections) {
+            connection.requestStop();
+        }
+        try {
+            new CompositeStoppable(connections).stop();
+        } finally {
+            connections.clear();
+        }
+    }
+
+    private class ConnectionAsyncStoppable implements AsyncStoppable {
+        private final MultiChannelConnection<Object> messageConnection;
+        private final AtomicReference<ObjectConnection> connectionRef;
+
+        public ConnectionAsyncStoppable(MultiChannelConnection<Object> messageConnection,
+                                        AtomicReference<ObjectConnection> connectionRef) {
+            this.messageConnection = messageConnection;
+            this.connectionRef = connectionRef;
+        }
+
+        public void requestStop() {
+            messageConnection.requestStop();
+        }
+
+        public void stop() {
+            try {
+                messageConnection.stop();
+            } finally {
+                connections.remove(connectionRef.get());
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
new file mode 100755
index 0000000..73fbd95
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.api.Action;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+
+import java.util.UUID;
+
+public class DefaultMultiChannelConnector implements MultiChannelConnector, Stoppable {
+    private final OutgoingConnector<Message> outgoingConnector;
+    private final ExecutorFactory executorFactory;
+    private final StoppableExecutor executorService;
+    private final HandshakeIncomingConnector incomingConnector;
+    private final IdGenerator<UUID> idGenerator;
+    private final ClassLoader messagingClassLoader;
+
+    public DefaultMultiChannelConnector(OutgoingConnector<Message> outgoingConnector, IncomingConnector<Message> incomingConnector,
+                                        ExecutorFactory executorFactory, ClassLoader messagingClassLoader, IdGenerator<UUID> idGenerator) {
+        this.messagingClassLoader = messagingClassLoader;
+        this.idGenerator = idGenerator;
+        this.outgoingConnector = new HandshakeOutgoingConnector(outgoingConnector);
+        this.executorFactory = executorFactory;
+        executorService = executorFactory.create("Incoming Connection Handler");
+        this.incomingConnector = new HandshakeIncomingConnector(incomingConnector, executorService);
+    }
+
+    public void stop() {
+        executorService.stop();
+    }
+
+    public Address accept(final Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
+        Action<ConnectEvent<Connection<Message>>> connectAction = new Action<ConnectEvent<Connection<Message>>>() {
+            public void execute(ConnectEvent<Connection<Message>> event) {
+                finishConnect(event, action);
+            }
+        };
+        return incomingConnector.accept(connectAction, false);
+    }
+
+    private void finishConnect(ConnectEvent<Connection<Message>> event,
+                               Action<ConnectEvent<MultiChannelConnection<Object>>> action) {
+        Address localAddress = event.getLocalAddress();
+        Address remoteAddress = event.getRemoteAddress();
+        MessageHub hub = new MessageHub(String.format("Incoming Connection %s", localAddress), "message server", executorFactory, idGenerator, messagingClassLoader);
+        DefaultMultiChannelConnection channelConnection = new DefaultMultiChannelConnection(hub, event.getConnection(), localAddress, remoteAddress);
+        action.execute(new ConnectEvent<MultiChannelConnection<Object>>(channelConnection, localAddress, remoteAddress));
+    }
+
+    public MultiChannelConnection<Object> connect(Address destinationAddress) {
+        Connection<Message> connection = outgoingConnector.connect(destinationAddress);
+        MessageHub hub = new MessageHub(String.format("Outgoing Connection %s", destinationAddress), "message client", executorFactory, idGenerator, messagingClassLoader);
+        return new DefaultMultiChannelConnection(hub, connection, null, destinationAddress);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultObjectConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
new file mode 100755
index 0000000..02f7685
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.Addressable;
+import org.gradle.messaging.remote.ObjectConnection;
+
+public class DefaultObjectConnection implements ObjectConnection {
+    private final Addressable addressable;
+    private final AsyncStoppable stopControl;
+    private final OutgoingMethodInvocationHandler outgoing;
+    private final IncomingMethodInvocationHandler incoming;
+
+    public DefaultObjectConnection(Addressable addressable, AsyncStoppable stopControl,
+                                   OutgoingMethodInvocationHandler outgoing, IncomingMethodInvocationHandler incoming) {
+        this.addressable = addressable;
+        this.stopControl = stopControl;
+        this.outgoing = outgoing;
+        this.incoming = incoming;
+    }
+
+    public Address getRemoteAddress() {
+        return addressable.getRemoteAddress();
+    }
+
+    public Address getLocalAddress() {
+        return addressable.getLocalAddress();
+    }
+
+    public <T> void addIncoming(Class<T> type, T instance) {
+        incoming.addIncoming(type, instance);
+    }
+
+    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
+        incoming.addIncoming(type, dispatch);
+    }
+
+    public <T> T addOutgoing(Class<T> type) {
+        return outgoing.addOutgoing(type);
+    }
+
+    public void requestStop() {
+        stopControl.requestStop();
+    }
+
+    public void stop() {
+        stopControl.stop();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
new file mode 100644
index 0000000..e0dc24a
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DefaultOutgoingBroadcast.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.DispatchFailureHandler;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.protocol.ChannelAvailable;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.messaging.remote.internal.protocol.LookupRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class DefaultOutgoingBroadcast implements OutgoingBroadcast, Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOutgoingBroadcast.class);
+    private final MessageOriginator messageOriginator;
+    private final String group;
+    private final OutgoingConnector<Message> outgoingConnector;
+    private final ProtocolStack<DiscoveryMessage> discoveryBroadcast;
+    private final Lock lock = new ReentrantLock();
+    private final StoppableExecutor executor;
+    private final Set<String> channels = new HashSet<String>();
+    private final Set<Address> connections = new HashSet<Address>();
+    private final MessageHub hub;
+
+    public DefaultOutgoingBroadcast(MessageOriginator messageOriginator, String group, AsyncConnection<DiscoveryMessage> connection, OutgoingConnector<Message> outgoingConnector, ExecutorFactory executorFactory, final IdGenerator<UUID> idGenerator, ClassLoader messagingClassLoader) {
+        this.messageOriginator = messageOriginator;
+        this.group = group;
+        this.outgoingConnector = outgoingConnector;
+        DispatchFailureHandler<Object> failureHandler = new DiscardingFailureHandler<Object>(LOGGER);
+
+        hub = new MessageHub("outgoing broadcast", messageOriginator.getName(), executorFactory, idGenerator, messagingClassLoader);
+
+        executor = executorFactory.create("broadcast lookup");
+        discoveryBroadcast = new ProtocolStack<DiscoveryMessage>(executor, failureHandler, failureHandler, new ChannelLookupProtocol());
+        connection.dispatchTo(new GroupMessageFilter(group, discoveryBroadcast.getBottom()));
+        discoveryBroadcast.getBottom().dispatchTo(connection);
+        discoveryBroadcast.getTop().dispatchTo(new DiscoveryMessageDispatch());
+
+        LOGGER.info("Created OutgoingBroadcast with {}", messageOriginator);
+    }
+
+    public <T> T addOutgoing(Class<T> type) {
+        String channelKey = type.getName();
+        lock.lock();
+        try {
+            if (channels.add(channelKey)) {
+                discoveryBroadcast.getTop().dispatch(new LookupRequest(messageOriginator, group, channelKey));
+            }
+        } finally {
+            lock.unlock();
+        }
+        return new ProxyDispatchAdapter<T>(hub.addMulticastOutgoing(channelKey), type).getSource();
+    }
+
+    public void stop() {
+        CompositeStoppable stoppable = new CompositeStoppable();
+        lock.lock();
+        try {
+            stoppable.add(hub, discoveryBroadcast, executor);
+        } finally {
+            connections.clear();
+            lock.unlock();
+        }
+        stoppable.stop();
+    }
+
+    private class DiscoveryMessageDispatch implements Dispatch<DiscoveryMessage> {
+        public void dispatch(DiscoveryMessage message) {
+            if (message instanceof ChannelAvailable) {
+                ChannelAvailable available = (ChannelAvailable) message;
+                Address serviceAddress = available.getAddress();
+                lock.lock();
+                try {
+                    if (!channels.contains(available.getChannel())) {
+                        return;
+                    }
+                    if (connections.contains(serviceAddress)) {
+                        return;
+                    }
+                    connections.add(serviceAddress);
+                } finally {
+                    lock.unlock();
+                }
+
+                Connection<Message> syncConnection = outgoingConnector.connect(serviceAddress);
+                hub.addConnection(syncConnection);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DelegatingConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DelegatingConnection.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DelegatingConnection.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DelegatingConnection.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnection.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java
new file mode 100644
index 0000000..8cfde2a
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecorator.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A {@link DisconnectAwareConnection} implementation that decorates an existing connection, adding disconnect awareness.
+ * <p>
+ * This implementation uses {@link EagerReceiveBuffer} internally to receive messages as fast as they are sent.
+ * The messages are then collected by {@link #receive()} as per normal.
+ * <p>
+ * NOTE: due to the use of a bounded buffer, disconnection may not be detected immediately if the internal buffer is full.
+ */
+public class DisconnectAwareConnectionDecorator<T> extends DelegatingConnection<T> implements DisconnectAwareConnection<T> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DisconnectAwareConnectionDecorator.class);
+    private static final int DEFAULT_BUFFER_SIZE = 200;
+
+    private final Lock actionLock = new ReentrantLock();
+    private final CountDownLatch actionSetLatch = new CountDownLatch(1);
+    private final EagerReceiveBuffer<T> receiveBuffer;
+    private Runnable disconnectAction;
+
+    private volatile boolean stopped;
+
+    public DisconnectAwareConnectionDecorator(Connection<T> connection, StoppableExecutor executor) {
+        this(connection, executor, DEFAULT_BUFFER_SIZE);
+    }
+
+    public DisconnectAwareConnectionDecorator(Connection<T> connection, StoppableExecutor executor, int bufferSize) {
+        super(connection);
+
+        // EagerReceiveBuffer guaranteest that onReceiversExhausted() will be completed before it returns null from receive(),
+        // which means we satisfy the condition of the DisconnectAwareConnection contract that disconnect handlers must complete
+        // before receive() returns null.
+
+        receiveBuffer = new EagerReceiveBuffer<T>(executor, bufferSize, connection, new Runnable() {
+            public void run() {
+                invokeDisconnectAction();
+            }
+        });
+
+        receiveBuffer.start();
+    }
+
+    public Runnable onDisconnect(Runnable disconnectAction) {
+        actionLock.lock();
+        try {
+            Runnable previous = disconnectAction;
+            this.disconnectAction = disconnectAction;
+            actionSetLatch.countDown();
+            return previous;
+        } finally {
+            actionLock.unlock();
+        }
+    }
+
+    public T receive() {
+        return receiveBuffer.receive();
+    }
+
+    private void invokeDisconnectAction() {
+        if (!stopped) {
+            try {
+                actionSetLatch.await();
+            } catch (InterruptedException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+
+            actionLock.lock();
+            try {
+                if (disconnectAction != null) {
+                    LOGGER.debug("about to invoke disconnection handler {}", disconnectAction);
+                    try {
+                        disconnectAction.run();
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                        LOGGER.error("disconnection handler threw exception", e);
+                        throw UncheckedException.throwAsUncheckedException(e);
+                    }
+                    LOGGER.info("completed disconnection handler {}", disconnectAction);
+                }
+            } finally {
+                actionLock.unlock();
+            }
+        }
+    }
+
+    public void requestStop() {
+        stopped = true;
+        onDisconnect(null);
+        super.requestStop();
+    }
+
+    public void stop() {
+        stopped = true;
+        onDisconnect(null);
+        super.stop();
+        receiveBuffer.stop();
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
new file mode 100644
index 0000000..9534a74
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/EagerReceiveBuffer.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.AsyncReceive;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.Receive;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Continuously consumes from on or more receivers, serialising to an in memory buffer for synchronous consumption.
+ * <p>
+ * Messages from the same receive instance are guaranteed to always be returned from {@link #receive()} in sequence. However, no
+ * guarantee is made to deliver messages from different sources in chronological order when multiple multiple receive instances
+ * are being consumed from.
+ * <p>
+ * The buffer is bounded, the size of which is specified at construction or defaulting to {@value #DEFAULT_BUFFER_SIZE}.
+ * If the buffer fills, the receive threads will block until space becomes available. If a stop is initiated while
+ * a thread is waiting for free space in the buffer after having received a message, that message will be discarded.
+ * <p>
+ * If a stop is initiated while a receive thread is waiting to receive (i.e. is blocked in a {@code receive()} call to the source),
+ * the stop will block until this returns. Therefore, it is advised to try to externally stop each of the receive instances being
+ * used by the buffer before initiating a stop on the buffer.
+ */
+public class EagerReceiveBuffer<T> implements Receive<T>, AsyncStoppable {
+
+    private enum State {
+        Init, Started, Stopping, Stopped
+    }
+
+    private static final int DEFAULT_BUFFER_SIZE = 200;
+
+    private final Lock lock = new ReentrantLock();
+    private final Condition notFullOrStop  = lock.newCondition();
+    private final Condition notEmptyOrNoReceivers = lock.newCondition();
+
+
+    private final StoppableExecutor executor;
+    private final int bufferSize;
+    private final Collection<Receive<T>> receivers;
+    private final Runnable onReceiversExhausted;
+    private final CountDownLatch onReceiversExhaustedFinishedLatch = new CountDownLatch(1);
+
+    private final AsyncReceive<T> asyncReceive;
+    private final LinkedList<T> queue = new LinkedList<T>();
+
+    private boolean hasActiveReceivers = true;
+    private State state = State.Init;
+
+    private static <T> Collection<Receive<T>> toReceiveCollection(Receive<T> receiver) {
+        Collection<Receive<T>> list = new ArrayList<Receive<T>>(1);
+        list.add(receiver);
+        return list;
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, Receive<T> receiver) {
+        this(executor, DEFAULT_BUFFER_SIZE, toReceiveCollection(receiver), null);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, Receive<T> receiver, Runnable onReceiversExhausted) {
+        this(executor, DEFAULT_BUFFER_SIZE, toReceiveCollection(receiver), onReceiversExhausted);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, Collection<Receive<T>> receivers) {
+        this(executor, DEFAULT_BUFFER_SIZE, receivers, null);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, Collection<Receive<T>> receivers, Runnable onReceiversExhausted) {
+        this(executor, DEFAULT_BUFFER_SIZE, receivers, onReceiversExhausted);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Receive<T> receiver) {
+        this(executor, bufferSize, toReceiveCollection(receiver), null);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Receive<T> receiver, Runnable onReceiversExhausted) {
+        this(executor, bufferSize, toReceiveCollection(receiver), onReceiversExhausted);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, int bufferSize, Collection<Receive<T>> receivers) {
+        this(executor, bufferSize, receivers, null);
+    }
+
+    public EagerReceiveBuffer(StoppableExecutor executor, final int bufferSize, Collection<Receive<T>> receivers, final Runnable onReceiversExhausted) {
+        if (receivers.size() == 0) {
+            throw new IllegalArgumentException("eager receive buffer created with no receivers");
+        }
+
+        if (bufferSize < 1) {
+            throw new IllegalArgumentException("eager receive buffer size must be positive (value given: " + bufferSize + ")");
+        }
+
+        this.executor = executor;
+        this.bufferSize = bufferSize;
+        this.receivers = receivers;
+        this.onReceiversExhausted = onReceiversExhausted;
+
+        Dispatch<T> dispatch = new Dispatch<T>() {
+            public void dispatch(T message) {
+                lock.lock();
+                try {
+                    while (queue.size() == bufferSize && state == State.Started) {
+                        try {
+                            notFullOrStop.await();
+                        } catch (InterruptedException e) {
+                            throw UncheckedException.throwAsUncheckedException(e);
+                        }
+                    }
+
+                    queue.add(message);
+                    notEmptyOrNoReceivers.signalAll();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        };
+
+        this.asyncReceive = new AsyncReceive(executor, dispatch, new Runnable() {
+            public void run() {
+                lock.lock();
+                try {
+                    hasActiveReceivers = false;
+                    if (onReceiversExhausted != null) {
+                        onReceiversExhausted.run();
+                    }
+                    notEmptyOrNoReceivers.signalAll();
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                } finally {
+                    lock.unlock();
+                    onReceiversExhaustedFinishedLatch.countDown();
+                }
+            }
+        });
+    }
+
+    /**
+     * Start consuming from the receivers given at construction.
+     *
+     * @throws IllegalStateException if already started
+     */
+    public void start() {
+        lock.lock();
+        try {
+            if (state != State.Init) {
+                throw new IllegalStateException("this eager receive buffer has already been started");
+            }
+            state = State.Started;
+
+            for (Receive<T> receiver : receivers) {
+                asyncReceive.receiveFrom(receiver);
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Receive the next message from the buffer.
+     *
+     * @return The next message or {@code null} if there are no more messages and no unexhausted receivers.
+     */
+    public T receive() {
+        lock.lock();
+        try {
+            while (queue.isEmpty() && hasActiveReceivers) {
+                try {
+                    notEmptyOrNoReceivers.await();
+                } catch (InterruptedException e) {
+                    throw UncheckedException.throwAsUncheckedException(e);
+                }
+            }
+
+            if (queue.isEmpty()) {
+                // no more messages, and all receivers are exhausted
+                assert !hasActiveReceivers;
+                return null;
+            } else {
+                T message = queue.poll();
+                assert message != null;
+                notFullOrStop.signalAll();
+                return message;
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Stops receiving new messages.
+     */
+    public void requestStop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void doRequestStop() {
+        asyncReceive.requestStop();
+        if (hasActiveReceivers) {
+            setState(State.Stopping);
+        } else {
+            setState(State.Stopped);
+        }
+    }
+
+    private void setState(State state) {
+        this.state = state;
+        notFullOrStop.signalAll(); // wake up any consumers waiting for space (assume it's a stopish state)
+    }
+
+    /**
+     * Stops receiving new messages. Blocks until all queued messages have been delivered.
+     */
+    public void stop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+
+        // Have to relinquish lock at this point because the onReceiversExhausted callback that we pass to the async
+        // runnable needs to acquire the lock in order to signal the notEmptyOrNoReceivers condition. If we didn't
+        // relinquish we would have deadlock. This is harmless due to this method being idempotent.
+        try {
+            onReceiversExhaustedFinishedLatch.await();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+        lock.lock();
+        try {
+            asyncReceive.stop();
+            setState(State.Stopped);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/GroupMessageFilter.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/GroupMessageFilter.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/GroupMessageFilter.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/GroupMessageFilter.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingBroadcast.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingBroadcast.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingBroadcast.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingBroadcast.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingConnector.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java
new file mode 100755
index 0000000..c43e0a4
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Message.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.io.ClassLoaderObjectInputStream;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+
+public abstract class Message implements Serializable {
+    public static void send(Object message, OutputStream outputSteam) throws IOException {
+        ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outputSteam);
+        try {
+            oos.writeObject(message);
+        } finally {
+            oos.flush();
+        }
+    }
+
+    public static Object receive(InputStream inputSteam, ClassLoader classLoader)
+            throws IOException, ClassNotFoundException {
+        ObjectInputStream ois = new ExceptionReplacingObjectInputStream(inputSteam, classLoader);
+        return ois.readObject();
+    }
+
+    private static class ExceptionPlaceholder implements Serializable {
+        private byte[] serializedException;
+        private String type;
+        private String message;
+        private ExceptionPlaceholder cause;
+        private StackTraceElement[] stackTrace;
+
+        public ExceptionPlaceholder(final Throwable throwable) throws IOException {
+            ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outstr) {
+                @Override
+                protected Object replaceObject(Object obj) throws IOException {
+                    if (obj == throwable) {
+                        return throwable;
+                    }
+                    // Don't serialize the cause - we'll serialize it separately later 
+                    if (obj == throwable.getCause()) {
+                        return new CausePlaceholder();
+                    }
+                    return super.replaceObject(obj);
+                }
+            };
+            try {
+                oos.writeObject(throwable);
+                oos.close();
+                serializedException = outstr.toByteArray();
+            } catch (NotSerializableException e) {
+                // Ignore
+            }
+
+            type = throwable.getClass().getName();
+            message = throwable.getMessage();
+            if (throwable.getCause() != null) {
+                cause = new ExceptionPlaceholder(throwable.getCause());
+            }
+            stackTrace = throwable.getStackTrace();
+        }
+
+        public Throwable read(ClassLoader classLoader) throws IOException {
+            final Throwable causeThrowable = getCause(classLoader);
+            Throwable throwable = null;
+            if (serializedException != null) {
+                try {
+                    final ExceptionReplacingObjectInputStream ois = new ExceptionReplacingObjectInputStream(new ByteArrayInputStream(serializedException), classLoader) {
+                        @Override
+                        protected Object resolveObject(Object obj) throws IOException {
+                            if (obj instanceof CausePlaceholder) {
+                                return causeThrowable;
+                            }
+                            return super.resolveObject(obj);
+                        }
+                    };
+                    throwable = (Throwable) ois.readObject();
+                } catch (ClassNotFoundException e) {
+                    // Ignore
+                } catch (InvalidClassException e) {
+                    try {
+                        Constructor<?> constructor = classLoader.loadClass(type).getConstructor(String.class);
+                        throwable = (Throwable) constructor.newInstance(message);
+                        throwable.initCause(causeThrowable);
+                        throwable.setStackTrace(stackTrace);
+                    } catch (ClassNotFoundException e1) {
+                        // Ignore
+                    } catch (NoSuchMethodException e1) {
+                        // Ignore
+                    } catch (Throwable t) {
+                        throw UncheckedException.throwAsUncheckedException(t);
+                    }
+                }
+            }
+
+            if (throwable == null) {
+                throwable = new PlaceholderException(type, message, causeThrowable);
+                throwable.setStackTrace(stackTrace);
+            }
+
+            return throwable;
+        }
+
+        private Throwable getCause(ClassLoader classLoader) throws IOException {
+            return cause != null ? cause.read(classLoader) : null;
+        }
+    }
+
+    private static class CausePlaceholder implements Serializable {
+    }
+
+    private static class TopLevelExceptionPlaceholder extends ExceptionPlaceholder {
+        private TopLevelExceptionPlaceholder(Throwable throwable) throws IOException {
+            super(throwable);
+        }
+    }
+
+    private static class ExceptionReplacingObjectOutputStream extends ObjectOutputStream {
+        public ExceptionReplacingObjectOutputStream(OutputStream outputSteam) throws IOException {
+            super(outputSteam);
+            enableReplaceObject(true);
+        }
+
+        @Override
+        protected Object replaceObject(Object obj) throws IOException {
+            if (obj instanceof Throwable) {
+                return new TopLevelExceptionPlaceholder((Throwable) obj);
+            }
+            return obj;
+        }
+    }
+
+    private static class ExceptionReplacingObjectInputStream extends ClassLoaderObjectInputStream {
+        public ExceptionReplacingObjectInputStream(InputStream inputSteam, ClassLoader classLoader) throws IOException {
+            super(inputSteam, classLoader);
+            enableResolveObject(true);
+        }
+
+        @Override
+        protected Object resolveObject(Object obj) throws IOException {
+            if (obj instanceof TopLevelExceptionPlaceholder) {
+                return ((ExceptionPlaceholder) obj).read(getClassLoader());
+            }
+            return obj;
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageHub.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageHub.java
new file mode 100644
index 0000000..23b59d2
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageHub.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.DispatchFailureHandler;
+import org.gradle.messaging.remote.internal.protocol.EndOfStreamEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class MessageHub implements AsyncStoppable {
+    private final Lock lock = new ReentrantLock();
+    private final CompositeStoppable executors = new CompositeStoppable();
+    private final CompositeStoppable connections = new CompositeStoppable();
+    private final Collection<ProtocolStack<Message>> handlers = new ArrayList<ProtocolStack<Message>>();
+    private final Collection<ProtocolStack<Message>> workers = new ArrayList<ProtocolStack<Message>>();
+    private final Map<String, ProtocolStack<Message>> outgoingUnicasts = new HashMap<String, ProtocolStack<Message>>();
+    private final Map<String, ProtocolStack<Message>> outgoingBroadcasts = new HashMap<String, ProtocolStack<Message>>();
+    private final DispatchFailureHandler<Object> failureHandler;
+    private final Router router;
+    private final String displayName;
+    private final String nodeName;
+    private final ExecutorFactory executorFactory;
+    private final IdGenerator<UUID> idGenerator;
+    private final ClassLoader messagingClassLoader;
+    private final StoppableExecutor incomingExecutor;
+
+    public MessageHub(String displayName, String nodeName, ExecutorFactory executorFactory, IdGenerator<UUID> idGenerator, ClassLoader messagingClassLoader) {
+        this.displayName = displayName;
+        this.nodeName = nodeName;
+        this.executorFactory = executorFactory;
+        this.idGenerator = idGenerator;
+        this.messagingClassLoader = messagingClassLoader;
+        failureHandler = new DiscardingFailureHandler<Object>(LoggerFactory.getLogger(MessageHub.class));
+        StoppableExecutor executor = executorFactory.create(displayName + " message router");
+        executors.add(executor);
+        router = new Router(executor, failureHandler);
+
+        incomingExecutor = executorFactory.create(displayName + " worker");
+        executors.add(incomingExecutor);
+    }
+
+    /**
+     * Adds an incoming connection. Stops the connection when finished with it.
+     */
+    public void addConnection(Connection<Message> connection) {
+        lock.lock();
+        try {
+            Connection<Message> wrapper = new EndOfStreamConnection(connection);
+            AsyncConnectionAdapter<Message> asyncConnection = new AsyncConnectionAdapter<Message>(wrapper, failureHandler, executorFactory, new RemoteDisconnectProtocol());
+            connections.add(asyncConnection);
+
+            AsyncConnection<Message> incomingEndpoint = router.createRemoteConnection();
+            incomingEndpoint.dispatchTo(new MethodInvocationMarshallingDispatch(asyncConnection));
+            asyncConnection.dispatchTo(new MethodInvocationUnmarshallingDispatch(incomingEndpoint, messagingClassLoader));
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public Dispatch<Object> addUnicastOutgoing(String channel) {
+        lock.lock();
+        try {
+            ProtocolStack<Message> outgoing = outgoingUnicasts.get(channel);
+            if (outgoing == null) {
+                Protocol<Message> unicastSendProtocol = new UnicastSendProtocol();
+                Protocol<Message> sendProtocol = new SendProtocol(idGenerator.generateId(), nodeName, channel);
+                StoppableExecutor executor = executorFactory.create(displayName + " outgoing " + channel);
+                executors.add(executor);
+                outgoing = new ProtocolStack<Message>(executor, failureHandler, failureHandler, unicastSendProtocol, sendProtocol);
+                outgoingUnicasts.put(channel, outgoing);
+
+                AsyncConnection<Message> outgoingEndpoint = router.createLocalConnection();
+                outgoing.getBottom().dispatchTo(outgoingEndpoint);
+                outgoingEndpoint.dispatchTo(outgoing.getBottom());
+            }
+            return new OutgoingMultiplex(channel, outgoing.getTop());
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public Dispatch<Object> addMulticastOutgoing(String channel) {
+        lock.lock();
+        try {
+            ProtocolStack<Message> outgoing = outgoingBroadcasts.get(channel);
+            if (outgoing == null) {
+                Protocol<Message> broadcastProtocol = new BroadcastSendProtocol();
+                Protocol<Message> sendProtocol = new SendProtocol(idGenerator.generateId(), nodeName, channel);
+                StoppableExecutor executor = executorFactory.create(displayName + " outgoing broadcast " + channel);
+                executors.add(executor);
+                outgoing = new ProtocolStack<Message>(executor, failureHandler, failureHandler, broadcastProtocol, sendProtocol);
+                outgoingBroadcasts.put(channel, outgoing);
+
+                AsyncConnection<Message> outgoingEndpoint = router.createLocalConnection();
+                outgoing.getBottom().dispatchTo(outgoingEndpoint);
+                outgoingEndpoint.dispatchTo(outgoing.getBottom());
+            }
+            return new OutgoingMultiplex(channel, outgoing.getTop());
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void addIncoming(final String channel, final Dispatch<Object> dispatch) {
+        lock.lock();
+        try {
+            final UUID id = idGenerator.generateId();
+            Protocol<Message> workerProtocol = new WorkerProtocol(dispatch);
+            Protocol<Message> receiveProtocol = new ReceiveProtocol(id, nodeName, channel);
+
+            ProtocolStack<Message> workerStack = new ProtocolStack<Message>(incomingExecutor, failureHandler, failureHandler, workerProtocol);
+            workers.add(workerStack);
+            ProtocolStack<Message> stack = new ProtocolStack<Message>(incomingExecutor, failureHandler, failureHandler, new BufferingProtocol(200), receiveProtocol);
+            handlers.add(stack);
+
+            workerStack.getBottom().dispatchTo(stack.getTop());
+            stack.getTop().dispatchTo(workerStack.getBottom());
+
+            AsyncConnection<Message> incomingEndpoint = router.createLocalConnection();
+            stack.getBottom().dispatchTo(incomingEndpoint);
+            incomingEndpoint.dispatchTo(stack.getBottom());
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void requestStop() {
+        lock.lock();
+        try {
+            for (ProtocolStack<Message> stack : outgoingUnicasts.values()) {
+                stack.requestStop();
+            }
+            for (ProtocolStack<Message> stack : outgoingBroadcasts.values()) {
+                stack.requestStop();
+            }
+            for (ProtocolStack<?> worker : workers) {
+                worker.requestStop();
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void stop() {
+        requestStop();
+
+        CompositeStoppable stoppable = new CompositeStoppable();
+        lock.lock();
+        try {
+            stoppable.add(outgoingUnicasts.values());
+            stoppable.add(outgoingBroadcasts.values());
+            stoppable.add(workers);
+            stoppable.add(handlers);
+            stoppable.add(connections);
+            stoppable.add(router);
+            stoppable.add(executors);
+        } finally {
+            outgoingUnicasts.clear();
+            outgoingBroadcasts.clear();
+            workers.clear();
+            handlers.clear();
+            lock.unlock();
+        }
+
+        stoppable.stop();
+    }
+
+    private static class EndOfStreamConnection extends DelegatingConnection<Message> {
+        private static final Logger LOGGER = LoggerFactory.getLogger(EndOfStreamConnection.class);
+        boolean incomingFinished;
+
+        private EndOfStreamConnection(Connection<Message> connection) {
+            super(connection);
+        }
+
+        @Override
+        public Message receive() {
+            if (incomingFinished) {
+                return null;
+            }
+            Message result;
+            try {
+                result = super.receive();
+            } catch (Throwable e) {
+                LOGGER.error("Could not receive message from connection. Discarding connection.", e);
+                result = null;
+            }
+            if (result instanceof EndOfStreamEvent) {
+                incomingFinished = true;
+            } else if (result == null) {
+                incomingFinished = true;
+                result = new EndOfStreamEvent();
+            }
+            return result;
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageIOException.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageIOException.java
new file mode 100644
index 0000000..6b8a66c
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageIOException.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+public class MessageIOException extends RuntimeException {
+    public MessageIOException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageOriginator.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageOriginator.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageOriginator.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageOriginator.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageSerializer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MessageSerializer.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessageSerializer.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java
new file mode 100644
index 0000000..9eac745
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MessagingServices.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.Stoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.id.UUIDGenerator;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.messaging.dispatch.DiscardingFailureHandler;
+import org.gradle.messaging.remote.MessagingClient;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.internal.inet.*;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryMessage;
+import org.gradle.messaging.remote.internal.protocol.DiscoveryProtocolSerializer;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.UUID;
+
+/**
+ * A factory for a set of messaging services. Provides the following services:
+ *
+ * <ul>
+ *
+ * <li>{@link MessagingClient}</li>
+ *
+ * <li>{@link MessagingServer}</li>
+ *
+ * <li>{@link OutgoingBroadcast}</li>
+ *
+ * <li>{@link IncomingBroadcast}</li>
+ *
+ * </ul>
+ */
+public class MessagingServices extends DefaultServiceRegistry implements Stoppable {
+    private final IdGenerator<UUID> idGenerator = new UUIDGenerator();
+    private final ClassLoader messageClassLoader;
+    private final String broadcastGroup;
+    private final SocketInetAddress broadcastAddress;
+    private DefaultMessagingClient messagingClient;
+    private DefaultMultiChannelConnector multiChannelConnector;
+    private TcpIncomingConnector<Message> incomingConnector;
+    private DefaultExecutorFactory executorFactory;
+    private DefaultMessagingServer messagingServer;
+    private DefaultIncomingBroadcast incomingBroadcast;
+    private AsyncConnectionAdapter<DiscoveryMessage> multicastConnection;
+    private DefaultOutgoingBroadcast outgoingBroadcast;
+
+    public MessagingServices(ClassLoader messageClassLoader) {
+        this(messageClassLoader, "gradle");
+    }
+
+    public MessagingServices(ClassLoader messageClassLoader, String broadcastGroup) {
+        this(messageClassLoader, broadcastGroup, defaultBroadcastAddress());
+    }
+
+    public MessagingServices(ClassLoader messageClassLoader, String broadcastGroup, SocketInetAddress broadcastAddress) {
+        this.messageClassLoader = messageClassLoader;
+        this.broadcastGroup = broadcastGroup;
+        this.broadcastAddress = broadcastAddress;
+    }
+
+    private static SocketInetAddress defaultBroadcastAddress() {
+        try {
+            return new SocketInetAddress(InetAddress.getByName("233.253.17.122"), 7912);
+        } catch (UnknownHostException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+    }
+
+    public void stop() {
+        close();
+    }
+
+    @Override
+    public void close() {
+        CompositeStoppable stoppable = new CompositeStoppable();
+        stoppable.add(incomingConnector);
+        stoppable.add(messagingClient);
+        stoppable.add(messagingServer);
+        stoppable.add(multiChannelConnector);
+        stoppable.add(outgoingBroadcast);
+        stoppable.add(incomingBroadcast);
+        stoppable.add(multicastConnection);
+        stoppable.add(executorFactory);
+        stoppable.stop();
+    }
+
+    protected MessageOriginator createMessageOriginator() {
+        String hostName = get(InetAddressFactory.class).getHostName();
+        String nodeName = String.format("%s@%s", System.getProperty("user.name"), hostName);
+        return new MessageOriginator(idGenerator.generateId(), nodeName);
+    }
+
+    protected ExecutorFactory createExecutorFactory() {
+        executorFactory = new DefaultExecutorFactory();
+        return executorFactory;
+    }
+
+    protected InetAddressFactory createInetAddressFactory() {
+        return new InetAddressFactory();
+    }
+
+    protected OutgoingConnector<Message> createOutgoingConnector() {
+        return new TcpOutgoingConnector<Message>(
+                new DefaultMessageSerializer<Message>(
+                        messageClassLoader));
+    }
+
+    protected IncomingConnector<Message> createIncomingConnector() {
+        incomingConnector = new TcpIncomingConnector<Message>(
+                get(ExecutorFactory.class),
+                new DefaultMessageSerializer<Message>(
+                        messageClassLoader),
+                get(InetAddressFactory.class),
+                idGenerator);
+        return incomingConnector;
+    }
+
+    protected MultiChannelConnector createMultiChannelConnector() {
+        multiChannelConnector = new DefaultMultiChannelConnector(
+                get(OutgoingConnector.class),
+                get(IncomingConnector.class),
+                get(ExecutorFactory.class),
+                messageClassLoader,
+                idGenerator);
+        return multiChannelConnector;
+    }
+
+    protected MessagingClient createMessagingClient() {
+        messagingClient = new DefaultMessagingClient(
+                get(MultiChannelConnector.class),
+                messageClassLoader);
+        return messagingClient;
+    }
+
+    protected MessagingServer createMessagingServer() {
+        messagingServer = new DefaultMessagingServer(
+                get(MultiChannelConnector.class),
+                messageClassLoader);
+        return messagingServer;
+    }
+
+    protected IncomingBroadcast createIncomingBroadcast() {
+        incomingBroadcast = new DefaultIncomingBroadcast(
+                get(MessageOriginator.class),
+                broadcastGroup,
+                get(AsyncConnection.class),
+                get(IncomingConnector.class),
+                get(ExecutorFactory.class),
+                idGenerator,
+                messageClassLoader);
+        return incomingBroadcast;
+    }
+
+    protected OutgoingBroadcast createOutgoingBroadcast() {
+        outgoingBroadcast = new DefaultOutgoingBroadcast(
+                get(MessageOriginator.class),
+                broadcastGroup,
+                get(AsyncConnection.class),
+                get(OutgoingConnector.class),
+                get(ExecutorFactory.class),
+                idGenerator,
+                messageClassLoader);
+        return outgoingBroadcast;
+    }
+
+    protected AsyncConnection<DiscoveryMessage> createMulticastConnection() {
+        MulticastConnection<DiscoveryMessage> connection = new MulticastConnection<DiscoveryMessage>(broadcastAddress, new DiscoveryProtocolSerializer());
+        multicastConnection = new AsyncConnectionAdapter<DiscoveryMessage>(
+                connection,
+                new DiscardingFailureHandler<DiscoveryMessage>(LoggerFactory.getLogger(MulticastConnection.class)),
+                get(ExecutorFactory.class));
+        return multicastConnection;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnection.java
new file mode 100755
index 0000000..9bad93b
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnection.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.messaging.remote.Addressable;
+import org.gradle.messaging.dispatch.Dispatch;
+
+public interface MultiChannelConnection<T> extends Addressable, AsyncStoppable {
+    /**
+     * Adds a destination for outgoing messages on the given channel. The returned value is thread-safe.
+     */
+    Dispatch<T> addOutgoingChannel(String channelKey);
+
+    /**
+     * Adds a handler for incoming messages on the given channel. The given dispatch is only ever used by a single
+     * thread at any given time.
+     */
+    void addIncomingChannel(String channelKey, Dispatch<T> dispatch);
+
+    /**
+     * Commences graceful stop of this connection. Stops accepting any more outgoing messages, and requests that the
+     * peer stop sending incoming messages.
+     */
+    void requestStop();
+
+    /**
+     * Performs a graceful stop of this connection. Blocks until all dispatched incoming messages have been handled, and
+     * all outgoing messages have been delivered.
+     */
+    void stop();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnector.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/MultiChannelConnector.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingBroadcast.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingBroadcast.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingBroadcast.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingBroadcast.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingConnector.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMultiplex.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingMultiplex.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMultiplex.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/OutgoingMultiplex.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java
new file mode 100755
index 0000000..de695cf
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/PlaceholderException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+/**
+ * A {@code PlaceholderException} is used when an exception cannot be serialized or deserialized.
+ */
+public class PlaceholderException extends RuntimeException {
+    private final String exceptionClassName;
+    
+    public PlaceholderException(String exceptionClassName, String message, Throwable cause) {
+        super(message, cause);
+        this.exceptionClassName = exceptionClassName;
+    }
+    
+    public String getExceptionClassName() {
+        return exceptionClassName;
+    }
+
+    public String toString() {
+        return String.format("%s: %s", exceptionClassName, getMessage());
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Protocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Protocol.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Protocol.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Protocol.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolContext.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolContext.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/ProtocolContext.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolContext.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolStack.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolStack.java
new file mode 100644
index 0000000..521e29e
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ProtocolStack.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.TrueTimeProvider;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.*;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class ProtocolStack<T> implements AsyncStoppable {
+    private final AsyncDispatch<Runnable> workQueue;
+    private final QueuingDispatch<T> incomingQueue = new QueuingDispatch<T>();
+    private final QueuingDispatch<T> outgoingQueue = new QueuingDispatch<T>();
+    private final AsyncReceive<Runnable> receiver;
+    private final DelayedReceive<Runnable> callbackQueue;
+    private final LinkedList<Stage> stack = new LinkedList<Stage>();
+    private final LinkedList<Runnable> contextQueue = new LinkedList<Runnable>();
+    private final DispatchFailureHandler<? super T> outgoingDispatchFailureHandler;
+    private final DispatchFailureHandler<? super T> incomingDispatchFailureHandler;
+    private final CountDownLatch protocolsStopped;
+    private final AtomicBoolean stopRequested = new AtomicBoolean();
+    private final AsyncConnection<T> bottomConnection;
+    private final AsyncConnection<T> topConnection;
+
+    public ProtocolStack(Executor executor, DispatchFailureHandler<? super T> outgoingDispatchFailureHandler, DispatchFailureHandler<? super T> incomingDispatchFailureHandler,
+                         Protocol<T>... protocols) {
+        this.outgoingDispatchFailureHandler = outgoingDispatchFailureHandler;
+        this.incomingDispatchFailureHandler = incomingDispatchFailureHandler;
+        this.callbackQueue = new DelayedReceive<Runnable>(new TrueTimeProvider());
+        protocolsStopped = new CountDownLatch(protocols.length);
+
+        //Start work queue
+        workQueue = new AsyncDispatch<Runnable>(executor);
+        workQueue.dispatchTo(new ExecuteRunnable());
+
+        stack.add(new TopStage());
+        for (Protocol<T> protocol : protocols) {
+            stack.add(new ProtocolStage(protocol));
+        }
+        stack.add(new BottomStage());
+        for (int i = 0; i < stack.size(); i++) {
+            Stage context = stack.get(i);
+            Stage outgoingStage = i == stack.size() - 1 ? null : stack.get(i + 1);
+            Stage incomingStage = i == 0 ? null : stack.get(i - 1);
+            context.attach(outgoingStage, incomingStage);
+        }
+
+        // Wire up callback queue
+        receiver = new AsyncReceive<Runnable>(executor);
+        receiver.dispatchTo(workQueue);
+        receiver.receiveFrom(callbackQueue);
+
+        bottomConnection = new BottomConnection();
+        topConnection = new TopConnection();
+
+        // Start each protocol from bottom to top
+        workQueue.dispatch(new Runnable() {
+            public void run() {
+                for (int i = stack.size() - 1; i >= 0; i--) {
+                    Stage context = stack.get(i);
+                    context.start();
+                }
+            }
+        });
+    }
+
+    public AsyncConnection<T> getBottom() {
+        return bottomConnection;
+    }
+
+    public AsyncConnection<T> getTop() {
+        return topConnection;
+    }
+
+    public void requestStop() {
+        if (!stopRequested.getAndSet(true)) {
+            workQueue.dispatch(new Runnable() {
+                public void run() {
+                    stack.getFirst().requestStop();
+                }
+            });
+        }
+    }
+
+    public void stop() {
+        requestStop();
+        try {
+            protocolsStopped.await();
+        } catch (InterruptedException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        callbackQueue.clear();
+        new CompositeStoppable(callbackQueue, receiver, workQueue, incomingQueue, outgoingQueue).stop();
+    }
+
+    private class ExecuteRunnable implements Dispatch<Runnable> {
+        public void dispatch(Runnable message) {
+            contextQueue.add(message);
+            while (!contextQueue.isEmpty()) {
+                contextQueue.removeFirst().run();
+            }
+        }
+    }
+
+    private abstract class Stage {
+        protected Stage outgoing;
+        protected Stage incoming;
+
+        public void attach(Stage outgoing, Stage incoming) {
+            this.outgoing = outgoing;
+            this.incoming = incoming;
+        }
+
+        public void start() {
+        }
+
+        public void handleIncoming(T message) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void handleOutgoing(T message) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void requestStop() {
+        }
+    }
+
+    private enum StageState { Init, StopRequested, StopPending, Stopped }
+
+    private class ProtocolStage extends Stage implements ProtocolContext<T> {
+        private final Protocol<T> protocol;
+        private StageState state = StageState.Init;
+
+        private ProtocolStage(Protocol<T> protocol) {
+            this.protocol = protocol;
+        }
+
+        @Override
+        public void start() {
+            protocol.start(this);
+        }
+
+        @Override
+        public void handleIncoming(T message) {
+            try {
+                protocol.handleIncoming(message);
+            } catch (Throwable throwable) {
+                incomingDispatchFailureHandler.dispatchFailed(message, throwable);
+            }
+        }
+
+        @Override
+        public void handleOutgoing(T message) {
+            try {
+                protocol.handleOutgoing(message);
+            } catch (Throwable throwable) {
+                outgoingDispatchFailureHandler.dispatchFailed(message, throwable);
+            }
+        }
+
+        public void dispatchIncoming(final T message) {
+            contextQueue.add(new Runnable() {
+                public void run() {
+                    incoming.handleIncoming(message);
+                }
+            });
+        }
+
+        public void dispatchOutgoing(final T message) {
+            contextQueue.add(new Runnable() {
+                public void run() {
+                    outgoing.handleOutgoing(message);
+                }
+            });
+        }
+
+        public Callback callbackLater(int delay, TimeUnit delayUnits, Runnable action) {
+            DefaultCallback callback = new DefaultCallback(action);
+            callbackQueue.dispatchLater(callback, delay, delayUnits);
+            return callback;
+        }
+
+        public void stopped() {
+            if (state == StageState.Init) {
+                throw new IllegalStateException(String.format("Cannot stop when in %s state.", state));
+            }
+            if (state != StageState.Stopped) {
+                state = StageState.Stopped;
+                protocolsStopped.countDown();
+                contextQueue.add(new Runnable() {
+                    public void run() {
+                        outgoing.requestStop();
+                    }
+                });
+            }
+        }
+
+        public void stopLater() {
+            if (state == StageState.Init || state == StageState.Stopped) {
+                throw new IllegalStateException(String.format("Cannot stop later when in %s state.", state));
+            }
+            state = StageState.StopPending;
+        }
+
+        @Override
+        public void requestStop() {
+            assert state == StageState.Init;
+            state = StageState.StopRequested;
+            protocol.stopRequested();
+            if (state == StageState.StopRequested) {
+                stopped();
+            }
+        }
+
+        private class DefaultCallback implements Runnable, ProtocolContext.Callback {
+            final Runnable action;
+            boolean cancelled;
+
+            private DefaultCallback(Runnable action) {
+                this.action = action;
+            }
+
+            public void cancel() {
+                cancelled = true;
+                callbackQueue.remove(this);
+            }
+
+            public void run() {
+                if (!cancelled && state != StageState.Stopped) {
+                    action.run();
+                }
+            }
+        }
+    }
+
+    private class TopStage extends Stage {
+        @Override
+        public void handleIncoming(T message) {
+            incomingQueue.dispatch(message);
+        }
+
+        @Override
+        public void handleOutgoing(T message) {
+            outgoing.handleOutgoing(message);
+        }
+
+        @Override
+        public void requestStop() {
+            outgoing.requestStop();
+        }
+    }
+
+    private class BottomStage extends Stage {
+        @Override
+        public void handleIncoming(T message) {
+            incoming.handleIncoming(message);
+        }
+
+        @Override
+        public void handleOutgoing(T message) {
+            outgoingQueue.dispatch(message);
+        }
+    }
+
+    private class BottomConnection implements AsyncConnection<T> {
+        public void dispatchTo(Dispatch<? super T> handler) {
+            outgoingQueue.dispatchTo(new FailureHandlingDispatch<T>(handler, outgoingDispatchFailureHandler));
+        }
+
+        public void dispatch(final T message) {
+            workQueue.dispatch(new Runnable() {
+                @Override
+                public String toString() {
+                    return String.format("incoming %s", message);
+                }
+
+                public void run() {
+                    stack.getLast().handleIncoming(message);
+                }
+            });
+        }
+    }
+
+    private class TopConnection implements AsyncConnection<T> {
+        public void dispatchTo(Dispatch<? super T> handler) {
+            incomingQueue.dispatchTo(new FailureHandlingDispatch<T>(handler, incomingDispatchFailureHandler));
+        }
+
+        public void dispatch(final T message) {
+            workQueue.dispatch(new Runnable() {
+                @Override
+                public String toString() {
+                    return String.format("outgoing %s", message);
+                }
+
+                public void run() {
+                    stack.getFirst().handleOutgoing(message);
+                }
+            });
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ReceiveProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ReceiveProtocol.java
new file mode 100644
index 0000000..792fba4
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/ReceiveProtocol.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.messaging.remote.internal.protocol.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+public class ReceiveProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ReceiveProtocol.class);
+    private final UUID id;
+    private final String displayName;
+    private final String channelKey;
+    private final Set<Object> producers = new HashSet<Object>();
+    private ProtocolContext<Message> context;
+    private boolean stopping;
+
+    public ReceiveProtocol(UUID id, String displayName, String channelKey) {
+        this.id = id;
+        this.displayName = displayName;
+        this.channelKey = channelKey;
+    }
+
+    public void start(ProtocolContext<Message> context) {
+        this.context = context;
+        LOGGER.debug("Starting receiver {}.", id);
+        context.dispatchOutgoing(new ConsumerAvailable(id, displayName, channelKey));
+    }
+
+    public void handleIncoming(Message message) {
+        if (message instanceof ProducerReady) {
+            LOGGER.debug("Producer ready: {}", message);
+            ProducerReady producerReady = (ProducerReady) message;
+            producers.add(producerReady.getProducerId());
+            context.dispatchOutgoing(new ConsumerReady(id, producerReady.getProducerId()));
+        } else if (message instanceof ProducerStopped) {
+            LOGGER.debug("Producer stopped: {}", message);
+            ProducerStopped producerStopped = (ProducerStopped) message;
+            context.dispatchOutgoing(new ConsumerStopped(id, producerStopped.getProducerId()));
+            removeProducer(producerStopped.getProducerId());
+        } else if (message instanceof ProducerUnavailable) {
+            LOGGER.debug("Producer unavailable: {}", message);
+            ProducerUnavailable producerUnavailable = (ProducerUnavailable) message;
+            removeProducer(producerUnavailable.getId());
+        } else if (message instanceof ProducerAvailable) {
+            // Ignore these broadcasts
+            return;
+        } else if (message instanceof Request) {
+            context.dispatchIncoming(message);
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected incoming message received: %s", message));
+        }
+    }
+
+    private void removeProducer(Object producerId) {
+        producers.remove(producerId);
+        if (stopping && producers.isEmpty()) {
+            LOGGER.debug("All producers finished. Stopping now.");
+            allProducersFinished();
+        }
+    }
+
+    public void handleOutgoing(Message message) {
+        if (message instanceof WorkerStopping) {
+            workerStopped();
+        } else if (message instanceof MessageCredits) {
+            LOGGER.debug("Discarding {}.", message);
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
+        }
+    }
+
+    private void workerStopped() {
+        stopping = true;
+        if (producers.isEmpty()) {
+            LOGGER.debug("No producers. Stopping now.");
+            allProducersFinished();
+            return;
+        }
+
+        LOGGER.debug("Waiting for producers to finish. Stopping later. Producers: {}", producers);
+        for (Object producer : producers) {
+            context.dispatchOutgoing(new ConsumerStopping(id, producer));
+        }
+    }
+
+    private void allProducersFinished() {
+        context.dispatchOutgoing(new ConsumerUnavailable(id));
+        context.dispatchIncoming(new EndOfStreamEvent());
+    }
+
+    public void stopRequested() {
+        assert stopping;
+        context.stopped();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/RemoteDisconnectProtocol.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocol.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/RemoteDisconnectProtocol.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Router.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Router.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/Router.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/Router.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/SendProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/SendProtocol.java
new file mode 100644
index 0000000..e6dfcfc
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/SendProtocol.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.messaging.remote.internal.protocol.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+public class SendProtocol implements Protocol<Message> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(SendProtocol.class);
+    private final String channelKey;
+    private final UUID id;
+    private final String displayName;
+    private ProtocolContext<Message> context;
+    private boolean stopping;
+    private final Map<Object, ConsumerAvailable> pending = new HashMap<Object, ConsumerAvailable>();
+    private final Set<Object> consumers = new HashSet<Object>();
+
+    public SendProtocol(UUID id, String displayName, String channelKey) {
+        this.channelKey = channelKey;
+        this.id = id;
+        this.displayName = displayName;
+    }
+
+    public void start(ProtocolContext<Message> context) {
+        LOGGER.debug("Starting producer {}", id);
+        this.context = context;
+        context.dispatchOutgoing(new ProducerAvailable(id, displayName, channelKey));
+    }
+
+    public void handleIncoming(Message message) {
+        if (message instanceof ConsumerAvailable) {
+            LOGGER.debug("Consumer available: {}", message);
+            ConsumerAvailable consumerAvailable = (ConsumerAvailable) message;
+            pending.put(consumerAvailable.getId(), consumerAvailable);
+            consumers.add(consumerAvailable.getId());
+            context.dispatchOutgoing(new ProducerReady(id, consumerAvailable.getId()));
+        } else if (message instanceof ConsumerReady) {
+            LOGGER.debug("Consumer ready: {}", message);
+            ConsumerReady consumerReady = (ConsumerReady) message;
+            context.dispatchIncoming(pending.remove(consumerReady.getConsumerId()));
+        } else if (message instanceof ConsumerStopping) {
+            LOGGER.debug("Consumer stopping: {}", message);
+            ConsumerStopping consumerStopping = (ConsumerStopping) message;
+            context.dispatchIncoming(new ConsumerUnavailable(consumerStopping.getConsumerId()));
+            context.dispatchOutgoing(new ProducerStopped(id, consumerStopping.getConsumerId()));
+        } else if (message instanceof ConsumerStopped) {
+            LOGGER.debug("Consumer stopped: {}", message);
+            ConsumerStopped consumerStopped = (ConsumerStopped) message;
+            consumers.remove(consumerStopped.getConsumerId());
+            maybeStop();
+        } else if (message instanceof ConsumerUnavailable) {
+            LOGGER.debug("Consumer unavailable: {}", message);
+            ConsumerUnavailable consumerUnavailable = (ConsumerUnavailable) message;
+            consumers.remove(consumerUnavailable.getId());
+            if (pending.remove(consumerUnavailable.getId()) == null) {
+                context.dispatchIncoming(new ConsumerUnavailable(consumerUnavailable.getId()));
+            }
+            maybeStop();
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected incoming message received: %s", message));
+        }
+    }
+
+    private void maybeStop() {
+        if (consumers.isEmpty() && stopping) {
+            LOGGER.debug("All consumers stopped. Stopping now.");
+            context.dispatchOutgoing(new ProducerUnavailable(id));
+            context.stopped();
+        }
+    }
+
+    public void handleOutgoing(Message message) {
+        if (message instanceof RoutableMessage) {
+            RoutableMessage routableMessage = (RoutableMessage) message;
+            if (!consumers.contains(routableMessage.getDestination())) {
+                throw new IllegalStateException(String.format("Message to unexpected destination dispatched: %s", message));
+            }
+            context.dispatchOutgoing(message);
+        } else {
+            throw new IllegalArgumentException(String.format("Unexpected outgoing message dispatched: %s", message));
+        }
+    }
+
+    public void stopRequested() {
+        stopping = true;
+        if (consumers.isEmpty()) {
+            maybeStop();
+            return;
+        }
+
+        LOGGER.debug("Waiting for consumers to stop: {}", consumers);
+        context.stopLater();
+        for (Object consumerId : consumers) {
+            context.dispatchOutgoing(new ProducerStopped(id, consumerId));
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TypeCastDispatch.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/TypeCastDispatch.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/TypeCastDispatch.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/TypeCastDispatch.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/UnicastSendProtocol.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocol.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/UnicastSendProtocol.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/WorkerProtocol.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/WorkerProtocol.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/WorkerProtocol.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/WorkerProtocol.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
new file mode 100644
index 0000000..03e674c
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetAddressFactory.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.inet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.*;
+
+/**
+ * Provides some information about the network addresses of the local machine.
+ */
+public class InetAddressFactory {
+    private static final Logger LOGGER = LoggerFactory.getLogger(InetAddressFactory.class);
+    private final Object lock = new Object();
+    private List<InetAddress> localAddresses;
+    private List<InetAddress> remoteAddresses;
+    private Collection<InetAddress> allAddresses;
+
+    /**
+     * Determines the name of the local machine.
+     */
+    public String getHostName() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException e) {
+            return findRemoteAddresses().get(0).toString();
+        }
+    }
+
+    /**
+     * Determines if the given source address is from the local machine.
+     */
+    public boolean isLocal(InetAddress address) {
+        try {
+            synchronized (lock) {
+                init();
+                return allAddresses.contains(address);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Could not determine the IP addresses for this machine.", e);
+        }
+    }
+
+    /**
+     * Locates all local (loopback) addresses for this machine. Never returns an empty list.
+     */
+    public List<InetAddress> findLocalAddresses() {
+        try {
+            synchronized (lock) {
+                init();
+                if (!localAddresses.isEmpty()) {
+                    return localAddresses;
+                }
+                InetAddress fallback = InetAddress.getByName(null);
+                LOGGER.debug("No loopback addresses, using fallback {}", fallback);
+                return Collections.singletonList(fallback);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Could not determine the local IP addresses for this machine.", e);
+        }
+    }
+
+    /**
+     * Locates the remote (non-loopback) addresses for this machine. Never returns an empty list.
+     */
+    public List<InetAddress> findRemoteAddresses() {
+        try {
+            synchronized (lock) {
+                init();
+                if (!remoteAddresses.isEmpty()) {
+                    return remoteAddresses;
+                }
+                InetAddress fallback = InetAddress.getLocalHost();
+                LOGGER.debug("No remote addresses, using fallback {}", fallback);
+                return Collections.singletonList(fallback);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Could not determine the remote IP addresses for this machine.", e);
+        }
+    }
+
+    private void init() throws Exception {
+        if (localAddresses != null) {
+            return;
+        }
+
+        Method loopbackMethod;
+        try {
+            loopbackMethod = NetworkInterface.class.getMethod("isLoopback");
+        } catch (NoSuchMethodException e) {
+            loopbackMethod = null;
+        }
+
+        localAddresses = new ArrayList<InetAddress>();
+        remoteAddresses = new ArrayList<InetAddress>();
+        allAddresses = new HashSet<InetAddress>();
+
+        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+        while (interfaces.hasMoreElements()) {
+            NetworkInterface networkInterface = interfaces.nextElement();
+            LOGGER.debug("Adding IP addresses for network interface {}", networkInterface.getName());
+            try {
+                Boolean isLoopbackInterface = null;
+                try {
+                    isLoopbackInterface = loopbackMethod == null ? null : (Boolean) loopbackMethod.invoke(networkInterface);
+                } catch (InvocationTargetException e) {
+                    if (!(e.getCause() instanceof SocketException)) {
+                        throw e.getCause();
+                    }
+                    // Ignore - treat as if we don't know
+                    isLoopbackInterface = null;
+                }
+                LOGGER.debug("Is this a loopback interface? {}", isLoopbackInterface);
+
+                Enumeration<InetAddress> candidates = networkInterface.getInetAddresses();
+                while (candidates.hasMoreElements()) {
+                    InetAddress candidate = candidates.nextElement();
+                    allAddresses.add(candidate);
+                    if (isLoopbackInterface == null) {
+                        // Don't know if this is a loopback interface or not
+                        if (candidate.isLoopbackAddress()) {
+                            LOGGER.debug("Adding loopback address {}", candidate);
+                            localAddresses.add(candidate);
+                        } else {
+                            LOGGER.debug("Adding non-loopback address {}", candidate);
+                            remoteAddresses.add(candidate);
+                        }
+                    } else if (isLoopbackInterface) {
+                        if (candidate.isLoopbackAddress()) {
+                            LOGGER.debug("Adding loopback address {}", candidate);
+                            localAddresses.add(candidate);
+                        } else {
+                            LOGGER.debug("Ignoring non-loopback address on loopback interface {}", candidate);
+                        }
+                    } else {
+                        if (candidate.isLoopbackAddress()) {
+                            LOGGER.debug("Ignoring loopback address on non-loopback interface {}", candidate);
+                        } else {
+                            LOGGER.debug("Adding non-loopback address {}", candidate);
+                            remoteAddresses.add(candidate);
+                        }
+                    }
+                }
+            } catch (Throwable e) {
+                throw new RuntimeException(String.format("Could not determine the IP addresses for network interface %s", networkInterface.getName()), e);
+            }
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetEndpoint.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetEndpoint.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/InetEndpoint.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/InetEndpoint.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddress.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MultiChoiceAddress.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddress.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MultiChoiceAddress.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MulticastConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
new file mode 100644
index 0000000..2c48478
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/MulticastConnection.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.inet;
+
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.MessageIOException;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+
+import java.io.*;
+import java.net.DatagramPacket;
+import java.net.MulticastSocket;
+import java.net.SocketException;
+
+public class MulticastConnection<T> implements Connection<T> {
+    private static final int MAX_MESSAGE_SIZE = 32*1024;
+    private final MulticastSocket socket;
+    private final SocketInetAddress address;
+    private final MessageSerializer<T> serializer;
+    private final SocketInetAddress localAddress;
+
+    public MulticastConnection(SocketInetAddress address, MessageSerializer<T> serializer) {
+        this.address = address;
+        this.serializer = serializer;
+        try {
+            socket = new MulticastSocket(address.getPort());
+            socket.joinGroup(address.getAddress());
+        } catch (IOException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        localAddress = new SocketInetAddress(socket.getInetAddress(), socket.getLocalPort());
+    }
+
+    @Override
+    public String toString() {
+        return String.format("multicast connection %s", address);
+    }
+
+    public void dispatch(T message) {
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
+            serializer.write(message, dataOutputStream);
+            dataOutputStream.close();
+            byte[] buffer = outputStream.toByteArray();
+            socket.send(new DatagramPacket(buffer, buffer.length, address.getAddress(), address.getPort()));
+        } catch (Exception e) {
+            throw new MessageIOException(String.format("Could not write multi-cast message on %s.", address), e);
+        }
+    }
+
+    public T receive() {
+        try {
+            byte[] buffer = new byte[MAX_MESSAGE_SIZE];
+            DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
+            socket.receive(packet);
+            ByteArrayInputStream inputStream = new ByteArrayInputStream(packet.getData(), packet.getOffset(), packet.getLength());
+            DataInputStream dataInputStream = new DataInputStream(inputStream);
+            return serializer.read(dataInputStream, localAddress, new SocketInetAddress(packet.getAddress(), packet.getPort()));
+        } catch (SocketException e) {
+            // Assume closed
+            return null;
+        } catch (Exception e) {
+            throw new MessageIOException(String.format("Could not receive multi-cast message on %s", address), e);
+        }
+    }
+
+    public void requestStop() {
+        socket.close();
+    }
+
+    public void stop() {
+        requestStop();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java
new file mode 100755
index 0000000..4690853
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketConnection.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal.inet;
+
+import com.google.common.base.Objects;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.MessageIOException;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+
+import java.io.*;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedSelectorException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+
+public class SocketConnection<T> implements Connection<T> {
+    private final SocketChannel socket;
+    private final SocketInetAddress localAddress;
+    private final SocketInetAddress remoteAddress;
+    private final MessageSerializer<T> serializer;
+    private final DataInputStream instr;
+    private final DataOutputStream outstr;
+
+    public SocketConnection(SocketChannel socket, MessageSerializer<T> serializer) {
+        this.socket = socket;
+        this.serializer = serializer;
+        try {
+            // NOTE: we use non-blocking IO as there is no reliable way when using blocking IO to shutdown reads while
+            // keeping writes active. For example, Socket.shutdownInput() does not work on Windows.
+            socket.configureBlocking(false);
+            outstr = new DataOutputStream(new SocketOutputStream(socket));
+            instr = new DataInputStream(new SocketInputStream(socket));
+        } catch (IOException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        InetSocketAddress localSocketAddress = (InetSocketAddress) socket.socket().getLocalSocketAddress();
+        localAddress = new SocketInetAddress(localSocketAddress.getAddress(), localSocketAddress.getPort());
+        InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
+        remoteAddress = new SocketInetAddress(remoteSocketAddress.getAddress(), remoteSocketAddress.getPort());
+    }
+
+    @Override
+    public String toString() {
+        return String.format("socket connection at %s with %s", localAddress, remoteAddress);
+    }
+
+    public Address getLocalAddress() {
+        return localAddress;
+    }
+
+    public Address getRemoteAddress() {
+        return remoteAddress;
+    }
+
+    public T receive() {
+        try {
+            return serializer.read(instr, localAddress, remoteAddress);
+        } catch (Exception e) {
+            if (isEndOfStream(e)) {
+                return null;
+            }
+            throw new MessageIOException(String.format("Could not read message from '%s'.", remoteAddress), e);
+        }
+    }
+
+    private boolean isEndOfStream(Exception e) {
+        if (e instanceof EOFException) {
+            return true;
+        }
+        if (e instanceof IOException) {
+            if (Objects.equal(e.getMessage(), "An existing connection was forcibly closed by the remote host")) {
+                return true;
+            }
+            if (Objects.equal(e.getMessage(), "An established connection was aborted by the software in your host machine")) {
+                return true;
+            }
+            if (Objects.equal(e.getMessage(), "Connection reset by peer")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void dispatch(T message) {
+        try {
+            serializer.write(message, outstr);
+            outstr.flush();
+        } catch (Exception e) {
+            throw new MessageIOException(String.format("Could not write message %s to '%s'.", message, remoteAddress), e);
+        }
+    }
+
+    public void requestStop() {
+        new CompositeStoppable(instr).stop();
+    }
+
+    public void stop() {
+        new CompositeStoppable(instr, outstr, socket).stop();
+    }
+
+    private static class SocketInputStream extends InputStream {
+        private final Selector selector;
+        private final ByteBuffer buffer;
+        private final SocketChannel socket;
+        private final byte[] readBuffer = new byte[1];
+
+        public SocketInputStream(SocketChannel socket) throws IOException {
+            this.socket = socket;
+            selector = Selector.open();
+            socket.register(selector, SelectionKey.OP_READ);
+            buffer = ByteBuffer.allocateDirect(4096);
+            buffer.limit(0);
+        }
+
+        @Override
+        public int read() throws IOException {
+            int nread = read(readBuffer, 0, 1);
+            if (nread <= 0) {
+                return nread;
+            }
+            return readBuffer[0];
+        }
+
+        @Override
+        public int read(byte[] dest, int offset, int max) throws IOException {
+            if (max == 0) {
+                return 0;
+            }
+
+            if (buffer.remaining() == 0) {
+                try {
+                    selector.select();
+                } catch (ClosedSelectorException e) {
+                    return -1;
+                }
+                if (!selector.isOpen()) {
+                    return -1;
+                }
+
+                buffer.clear();
+                int nread = socket.read(buffer);
+                buffer.flip();
+
+                if (nread < 0) {
+                    return -1;
+                }
+            }
+
+            int count = Math.min(buffer.remaining(), max);
+            buffer.get(dest, offset, count);
+            return count;
+        }
+
+        @Override
+        public void close() throws IOException {
+            selector.close();
+        }
+    }
+
+    private static class SocketOutputStream extends OutputStream {
+        private final Selector selector;
+        private final SocketChannel socket;
+        private final ByteBuffer buffer;
+        private final byte[] writeBuffer = new byte[1];
+
+        public SocketOutputStream(SocketChannel socket) throws IOException {
+            this.socket = socket;
+            selector = Selector.open();
+            socket.register(selector, SelectionKey.OP_WRITE);
+            buffer = ByteBuffer.allocateDirect(4096);
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            writeBuffer[0] = (byte) b;
+            write(writeBuffer);
+        }
+
+        @Override
+        public void write(byte[] src, int offset, int max) throws IOException {
+            int remaining = max;
+            int currentPos = offset;
+            while (remaining > 0) {
+                int count = Math.min(remaining, buffer.remaining());
+                if (count > 0) {
+                    buffer.put(src, currentPos, count);
+                    remaining -= count;
+                    currentPos += count;
+                }
+                if (buffer.remaining() == 0) {
+                    flush();
+                }
+            }
+        }
+
+        @Override
+        public void flush() throws IOException {
+            buffer.flip();
+            while (buffer.remaining() > 0) {
+                selector.select();
+                if (!selector.isOpen()) {
+                    throw new EOFException();
+                }
+                socket.write(buffer);
+            }
+            buffer.clear();
+        }
+
+        @Override
+        public void close() throws IOException {
+            selector.close();
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddress.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketInetAddress.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddress.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/SocketInetAddress.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
new file mode 100755
index 0000000..3cd9ebd
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpIncomingConnector.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal.inet;
+
+import org.gradle.api.Action;
+import org.gradle.internal.CompositeStoppable;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.IncomingConnector;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class TcpIncomingConnector<T> implements IncomingConnector<T>, AsyncStoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TcpIncomingConnector.class);
+    private final StoppableExecutor executor;
+    private final MessageSerializer<T> serializer;
+    private final InetAddressFactory addressFactory;
+    private final IdGenerator<?> idGenerator;
+    private final List<ServerSocketChannel> serverSockets = new CopyOnWriteArrayList<ServerSocketChannel>();
+
+    public TcpIncomingConnector(ExecutorFactory executorFactory, MessageSerializer<T> serializer, InetAddressFactory addressFactory, IdGenerator<?> idGenerator) {
+        this.serializer = serializer;
+        this.addressFactory = addressFactory;
+        this.idGenerator = idGenerator;
+        this.executor = executorFactory.create("Incoming TCP Connector");
+    }
+
+    public Address accept(Action<ConnectEvent<Connection<T>>> action, boolean allowRemote) {
+        ServerSocketChannel serverSocket;
+        int localPort;
+        try {
+            serverSocket = ServerSocketChannel.open();
+            serverSockets.add(serverSocket);
+            serverSocket.socket().bind(new InetSocketAddress(0));
+            localPort = serverSocket.socket().getLocalPort();
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+
+        Object id = idGenerator.generateId();
+        List<InetAddress> addresses = allowRemote ? addressFactory.findRemoteAddresses() : addressFactory.findLocalAddresses();
+        Address address = new MultiChoiceAddress(id, localPort, addresses);
+        LOGGER.debug("Listening on {}.", address);
+
+        executor.execute(new Receiver(serverSocket, action, allowRemote));
+        return address;
+    }
+
+    public void requestStop() {
+        new CompositeStoppable().add(serverSockets).stop();
+    }
+
+    public void stop() {
+        requestStop();
+        executor.stop();
+    }
+
+    private class Receiver implements Runnable {
+        private final ServerSocketChannel serverSocket;
+        private final Action<ConnectEvent<Connection<T>>> action;
+        private final boolean allowRemote;
+
+        public Receiver(ServerSocketChannel serverSocket, Action<ConnectEvent<Connection<T>>> action, boolean allowRemote) {
+            this.serverSocket = serverSocket;
+            this.action = action;
+            this.allowRemote = allowRemote;
+        }
+
+        public void run() {
+            try {
+                try {
+                    while (true) {
+                        SocketChannel socket = serverSocket.accept();
+                        InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
+                        InetAddress remoteInetAddress = remoteSocketAddress.getAddress();
+                        if (!allowRemote && !addressFactory.isLocal(remoteInetAddress)) {
+                            LOGGER.error("Cannot accept connection from remote address {}.", remoteInetAddress);
+                            socket.close();
+                            continue;
+                        }
+
+                        SocketConnection<T> connection = new SocketConnection<T>(socket, serializer);
+                        Address localAddress = connection.getLocalAddress();
+                        Address remoteAddress = connection.getRemoteAddress();
+
+                        LOGGER.debug("Accepted connection from {} to {}.", remoteAddress, localAddress);
+                        action.execute(new ConnectEvent<Connection<T>>(connection, localAddress, remoteAddress));
+                    }
+                } catch (ClosedChannelException e) {
+                    // Ignore
+                } catch (Exception e) {
+                    LOGGER.error("Could not accept remote connection.", e);
+                }
+            } finally {
+                new CompositeStoppable(serverSocket).stop();
+                serverSockets.remove(serverSocket);
+            }
+        }
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
new file mode 100755
index 0000000..330810f
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/inet/TcpOutgoingConnector.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal.inet;
+
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.internal.ConnectException;
+import org.gradle.messaging.remote.internal.Connection;
+import org.gradle.messaging.remote.internal.MessageSerializer;
+import org.gradle.messaging.remote.internal.OutgoingConnector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+public class TcpOutgoingConnector<T> implements OutgoingConnector<T> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TcpOutgoingConnector.class);
+    private final MessageSerializer<T> serializer;
+
+    public TcpOutgoingConnector(MessageSerializer<T> serializer) {
+        this.serializer = serializer;
+    }
+
+    public Connection<T> connect(Address destinationAddress) {
+        if (!(destinationAddress instanceof InetEndpoint)) {
+            throw new IllegalArgumentException(String.format("Cannot create a connection to address of unknown type: %s.", destinationAddress));
+        }
+        InetEndpoint address = (InetEndpoint) destinationAddress;
+        LOGGER.debug("Attempting to connect to {}.", address);
+
+        // Try each address in turn. Not all of them are necessarily reachable (eg when socket option IPV6_V6ONLY
+        // is on - the default for debian and others), so we will try each of them until we can connect
+        List<InetAddress> candidateAddresses = address.getCandidates();
+
+        // Now try each address
+        try {
+            Exception lastFailure = null;
+            for (InetAddress candidate : candidateAddresses) {
+                LOGGER.debug("Trying to connect to address {}.", candidate);
+                SocketChannel socketChannel;
+                try {
+                    socketChannel = SocketChannel.open(new InetSocketAddress(candidate, address.getPort()));
+                } catch (java.net.ConnectException e) {
+                    LOGGER.debug("Cannot connect to address {}, skipping.", candidate);
+                    lastFailure = e;
+                    continue;
+                } catch (SocketException e) {
+                    LOGGER.debug("Cannot connect to address {}, skipping.", candidate);
+                    lastFailure = new RuntimeException(String.format("Could not connect to address %s.", candidate), e);
+                    continue;
+                }
+                LOGGER.debug("Connected to address {}.", candidate);
+                return new SocketConnection<T>(socketChannel, serializer);
+            }
+            throw lastFailure;
+        } catch (java.net.ConnectException e) {
+            throw new ConnectException(String.format("Could not connect to server %s. Tried addresses: %s.",
+                    destinationAddress, candidateAddresses), e);
+        } catch (Exception e) {
+            throw new RuntimeException(String.format("Could not connect to server %s. Tried addresses: %s.",
+                    destinationAddress, candidateAddresses), e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessage.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessage.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessage.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelAvailable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ChannelAvailable.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelAvailable.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ChannelAvailable.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelUnavailable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ChannelUnavailable.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ChannelUnavailable.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ChannelUnavailable.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConnectRequest.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java
new file mode 100644
index 0000000..16efb01
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerAvailable.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ConsumerAvailable extends ParticipantAvailable {
+    public ConsumerAvailable(UUID id, String displayName, String channelKey) {
+        super(id, displayName, channelKey);
+    }
+
+    public RouteUnavailableMessage getUnavailableMessage() {
+        return new ConsumerUnavailable(getId());
+    }
+
+    public boolean acceptIncoming(RouteAvailableMessage message) {
+        if (message instanceof ProducerAvailable) {
+            ProducerAvailable producerAvailable = (ProducerAvailable) message;
+            return producerAvailable.getChannelKey().equals(getChannelKey());
+        }
+        return false;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java
new file mode 100644
index 0000000..c6b34bc
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerMessage.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+import java.util.UUID;
+
+public abstract class ConsumerMessage extends Message implements RoutableMessage {
+    protected final UUID consumerId;
+    protected final Object producerId;
+
+    public ConsumerMessage(UUID consumerId, Object producerId) {
+        this.producerId = producerId;
+        this.consumerId = consumerId;
+    }
+
+    public UUID getConsumerId() {
+        return consumerId;
+    }
+
+    public Object getProducerId() {
+        return producerId;
+    }
+
+    public Object getDestination() {
+        return producerId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        ConsumerMessage other = (ConsumerMessage) o;
+        return consumerId.equals(other.consumerId) && producerId.equals(other.producerId);
+    }
+
+    @Override
+    public int hashCode() {
+        return consumerId.hashCode() ^ producerId.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s, consumerId: %s, producerId: %s]", getClass().getSimpleName(), consumerId, producerId);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java
new file mode 100644
index 0000000..3b17611
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerReady.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ConsumerReady extends ConsumerMessage {
+    public ConsumerReady(UUID consumerId, Object producerId) {
+        super(consumerId, producerId);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java
new file mode 100644
index 0000000..7dfcc12
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerStopped.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ConsumerStopped extends ConsumerMessage {
+    public ConsumerStopped(UUID consumerId, Object producerId) {
+        super(consumerId, producerId);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java
new file mode 100644
index 0000000..c678b83
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerStopping.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ConsumerStopping extends ConsumerMessage {
+    public ConsumerStopping(UUID consumerId, Object producerId) {
+        super(consumerId, producerId);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java
new file mode 100644
index 0000000..b59df65
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ConsumerUnavailable.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ConsumerUnavailable extends ParticipantUnavailable {
+    public ConsumerUnavailable(UUID id) {
+        super(id);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryMessage.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/DiscoveryProtocolSerializer.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/EndOfStreamEvent.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/EndOfStreamEvent.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/EndOfStreamEvent.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/EndOfStreamEvent.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/LookupRequest.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/LookupRequest.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/LookupRequest.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/LookupRequest.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MessageCredits.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/MessageCredits.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MessageCredits.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/MessageCredits.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/MethodMetaInfo.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java
new file mode 100644
index 0000000..8c5482f
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ParticipantAvailable.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+import java.util.UUID;
+
+public abstract class ParticipantAvailable extends Message implements RouteAvailableMessage {
+    private final String channelKey;
+    private final UUID id;
+    private final String displayName;
+
+    public ParticipantAvailable(UUID id, String displayName, String channelKey) {
+        this.id = id;
+        this.displayName = displayName;
+        this.channelKey = channelKey;
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getChannelKey() {
+        return channelKey;
+    }
+
+    public Object getSource() {
+        return id;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s id: %s, displayName: %s, channel: %s]", getClass().getSimpleName(), id, displayName, channelKey);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        ParticipantAvailable other = (ParticipantAvailable) o;
+        return id.equals(other.id) && displayName.equals(other.displayName) && channelKey.equals(other.channelKey);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode() ^ displayName.hashCode() ^ channelKey.hashCode();
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java
new file mode 100644
index 0000000..370ae5b
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ParticipantUnavailable.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+import java.util.UUID;
+
+public class ParticipantUnavailable extends Message implements RouteUnavailableMessage {
+    private final UUID id;
+
+    public ParticipantUnavailable(UUID id) {
+        this.id = id;
+    }
+
+    public UUID getId() {
+        return id;
+    }
+
+    public Object getSource() {
+        return id;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s id: %s]", getClass().getSimpleName(), id);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        ParticipantUnavailable other = (ParticipantUnavailable) o;
+        return id.equals(other.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/PayloadMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/PayloadMessage.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/PayloadMessage.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/PayloadMessage.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java
new file mode 100644
index 0000000..d08d54b
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerAvailable.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ProducerAvailable extends ParticipantAvailable {
+    public ProducerAvailable(UUID id, String displayName, String channelKey) {
+        super(id, displayName, channelKey);
+    }
+
+    public RouteUnavailableMessage getUnavailableMessage() {
+        return new ProducerUnavailable(getId());
+    }
+
+    public boolean acceptIncoming(RouteAvailableMessage message) {
+        if (message instanceof ConsumerAvailable) {
+            ConsumerAvailable consumerAvailable = (ConsumerAvailable) message;
+            return consumerAvailable.getChannelKey().equals(getChannelKey());
+        }
+        return false;
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java
new file mode 100644
index 0000000..4cd12f7
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerMessage.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import org.gradle.messaging.remote.internal.Message;
+
+import java.util.UUID;
+
+public abstract class ProducerMessage extends Message implements RoutableMessage {
+    protected final UUID producerId;
+    protected final Object consumerId;
+
+    public ProducerMessage(UUID producerId, Object consumerId) {
+        this.consumerId = consumerId;
+        this.producerId = producerId;
+    }
+
+    public Object getConsumerId() {
+        return consumerId;
+    }
+
+    public UUID getProducerId() {
+        return producerId;
+    }
+
+    public Object getDestination() {
+        return consumerId;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false;
+        }
+        ProducerMessage other = (ProducerMessage) o;
+        return consumerId.equals(other.consumerId) && producerId.equals(other.producerId);
+    }
+
+    @Override
+    public int hashCode() {
+        return consumerId.hashCode() ^ producerId.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("[%s producerId: %s, consumerId: %s", getClass().getSimpleName(), producerId, consumerId);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerReady.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerReady.java
new file mode 100644
index 0000000..1b43f4a
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerReady.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ProducerReady extends ProducerMessage {
+    public ProducerReady(UUID producerId, Object consumerId) {
+        super(producerId, consumerId);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java
new file mode 100644
index 0000000..57dbf02
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerStopped.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ProducerStopped extends ProducerMessage {
+    public ProducerStopped(UUID producerId, Object consumerId) {
+        super(producerId, consumerId);
+    }
+}
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java
new file mode 100644
index 0000000..df8e6cd
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/ProducerUnavailable.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol;
+
+import java.util.UUID;
+
+public class ProducerUnavailable extends ParticipantUnavailable {
+    public ProducerUnavailable(UUID id) {
+        super(id);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocation.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/Request.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/Request.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/Request.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/Request.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RoutableMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RoutableMessage.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RoutableMessage.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RoutableMessage.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteAvailableMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RouteAvailableMessage.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteAvailableMessage.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RouteAvailableMessage.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteUnavailableMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RouteUnavailableMessage.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/RouteUnavailableMessage.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/RouteUnavailableMessage.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/StatelessMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/StatelessMessage.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/StatelessMessage.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/StatelessMessage.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/UnknownMessage.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/UnknownMessage.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/UnknownMessage.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/UnknownMessage.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopped.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/WorkerStopped.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopped.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/WorkerStopped.java
diff --git a/subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopping.java b/subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/WorkerStopping.java
similarity index 100%
rename from subprojects/core/src/main/groovy/org/gradle/messaging/remote/internal/protocol/WorkerStopping.java
rename to subprojects/messaging/src/main/java/org/gradle/messaging/remote/internal/protocol/WorkerStopping.java
diff --git a/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java
new file mode 100644
index 0000000..d349f54
--- /dev/null
+++ b/subprojects/messaging/src/main/java/org/gradle/messaging/serialize/Serializer.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2009 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 org.gradle.messaging.serialize;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public interface Serializer<T> {
+    T read(InputStream instr) throws Exception;
+
+    void write(OutputStream outstr, T value) throws Exception;
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatchTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatchTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatchTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatchTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandlerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandlerTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandlerTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingFailureHandlerTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatchTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatchTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatchTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/FailureHandlingDispatchTest.groovy
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java
new file mode 100755
index 0000000..71919c2
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.dispatch;
+
+import org.junit.Test;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class MethodInvocationTest {
+    @Test
+    public void equalsAndHashCode() throws Exception {
+        MethodInvocation invocation = new MethodInvocation(String.class.getMethod("length"), new Object[]{"param"});
+        MethodInvocation equalInvocation = new MethodInvocation(String.class.getMethod("length"), new Object[]{"param"});
+        MethodInvocation differentMethod = new MethodInvocation(String.class.getMethod("getBytes"), new Object[]{"param"});
+        MethodInvocation differentArgs = new MethodInvocation(String.class.getMethod("length"), new Object[]{"a", "b"});
+        assertThat(invocation, strictlyEqual(equalInvocation));
+        assertThat(invocation, not(equalTo(differentMethod)));
+        assertThat(invocation, not(equalTo(differentArgs)));
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/QueuingDispatchTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/QueuingDispatchTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/dispatch/QueuingDispatchTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/dispatch/QueuingDispatchTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapterTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapterTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapterTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/AsyncConnectionAdapterTest.groovy
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy
new file mode 100644
index 0000000..3a154e5
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/BroadcastSendProtocolTest.groovy
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal
+
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.Request
+import org.gradle.messaging.remote.internal.protocol.ConsumerAvailable
+import org.gradle.messaging.remote.internal.protocol.ConsumerUnavailable
+import java.util.concurrent.TimeUnit
+
+class BroadcastSendProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final BroadcastSendProtocol protocol = new BroadcastSendProtocol()
+    final UUID id = new UUID(0, 0)
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "queues outgoing messages until a consumer is available"() {
+        when:
+        protocol.handleOutgoing(new Request("channel", "message1"))
+        protocol.handleOutgoing(new Request("channel", "message2"))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request(id, "message1"))
+        1 * context.dispatchOutgoing(new Request(id, "message2"))
+        0 * context._
+    }
+
+    def "dispatches outgoing message to each consumer"() {
+        def id1 = new UUID(0, 1)
+        def id2 = new UUID(0, 2)
+
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(id1, "display", "channel"))
+        protocol.handleIncoming(new ConsumerAvailable(id2, "display", "channel"))
+
+        when:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request(id1, "message"))
+        1 * context.dispatchOutgoing(new Request(id2, "message"))
+        0 * context._
+    }
+
+    def "stops dispatching to a consumer when it becomes unavailable"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable(id))
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        then:
+        0 * context._
+    }
+
+    def "stop waits until for a consumer to become available and queued messages dispatched"() {
+        given:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopLater()
+        1 * context.callbackLater(5, TimeUnit.SECONDS, !null)
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request(id, "message"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stop waits until timeout for a consumer to become available and queued messages dispatched"() {
+        Runnable callback
+
+        when:
+        protocol.handleOutgoing(new Request("channel", "message"))
+        protocol.stopRequested()
+
+        then:
+        1 * context.callbackLater(5, TimeUnit.SECONDS, !null) >> { callback = it[2]; return null }
+
+        when:
+        callback.run()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when no messages have been dispatched"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when all messages have been dispatched"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BufferingProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/BufferingProtocolTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/BufferingProtocolTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/BufferingProtocolTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocolTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocolTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ChannelLookupProtocolTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocolTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocolTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ChannelRegistrationProtocolTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/CompositeAddressTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
new file mode 100755
index 0000000..6baa1bf
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal;
+
+import org.gradle.internal.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.Address;
+import org.gradle.messaging.remote.Addressable;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.gradle.util.Matchers.strictlyEqual;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+ at RunWith(JMock.class)
+public class DefaultObjectConnectionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private DefaultObjectConnection sender;
+    private DefaultObjectConnection receiver;
+    private final Addressable messageConnection = context.mock(Addressable.class);
+    private final AsyncStoppable stopControl = context.mock(AsyncStoppable.class);
+    private final TestConnection connection = new TestConnection();
+
+    @Before
+    public void setUp() {
+        IncomingMethodInvocationHandler senderIncoming = new IncomingMethodInvocationHandler(connection.getSender());
+        IncomingMethodInvocationHandler receiverIncoming = new IncomingMethodInvocationHandler(connection.getReceiver());
+        OutgoingMethodInvocationHandler senderOutgoing = new OutgoingMethodInvocationHandler(connection.getSender());
+        OutgoingMethodInvocationHandler receiverOutgoing = new OutgoingMethodInvocationHandler(connection.getReceiver());
+        sender = new DefaultObjectConnection(messageConnection, stopControl, senderOutgoing, senderIncoming);
+        receiver = new DefaultObjectConnection(messageConnection, stopControl, receiverOutgoing, receiverIncoming);
+    }
+
+    @Test
+    public void createsProxyForOutgoingType() throws Exception {
+        // Setup
+        final TestRemote handler = context.mock(TestRemote.class);
+        receiver.addIncoming(TestRemote.class, handler);
+
+        TestRemote proxy = sender.addOutgoing(TestRemote.class);
+        assertThat(proxy, strictlyEqual(proxy));
+        assertThat(proxy.toString(), equalTo("TestRemote broadcast"));
+    }
+
+    @Test
+    public void deliversMethodInvocationsOnOutgoingObjectToHandlerObject() throws Exception {
+        final TestRemote handler = context.mock(TestRemote.class);
+        context.checking(new Expectations() {{
+            one(handler).doStuff("param");
+        }});
+        receiver.addIncoming(TestRemote.class, handler);
+
+        TestRemote proxy = sender.addOutgoing(TestRemote.class);
+        proxy.doStuff("param");
+    }
+
+    @Test
+    public void deliversMethodInvocationsOnOutgoingObjectToHandlerDispatch() throws Exception {
+        final Dispatch<MethodInvocation> handler = context.mock(Dispatch.class);
+        context.checking(new Expectations() {{
+            one(handler).dispatch(new MethodInvocation(TestRemote.class.getMethod("doStuff", String.class),
+                    new Object[]{"param"}));
+        }});
+        receiver.addIncoming(TestRemote.class, handler);
+
+        TestRemote proxy = sender.addOutgoing(TestRemote.class);
+        proxy.doStuff("param");
+    }
+
+    @Test
+    public void canHaveMultipleOutgoingTypes() throws Exception {
+        final TestRemote handler1 = context.mock(TestRemote.class);
+        final TestRemote2 handler2 = context.mock(TestRemote2.class);
+
+        context.checking(new Expectations() {{
+            one(handler1).doStuff("handler 1");
+            one(handler2).doStuff("handler 2");
+        }});
+        receiver.addIncoming(TestRemote.class, handler1);
+        receiver.addIncoming(TestRemote2.class, handler2);
+
+        TestRemote remote1 = sender.addOutgoing(TestRemote.class);
+        TestRemote2 remote2 = sender.addOutgoing(TestRemote2.class);
+
+        remote1.doStuff("handler 1");
+        remote2.doStuff("handler 2");
+    }
+
+    @Test
+    public void handlesTypesWithSuperTypes() {
+        final TestRemote3 handler = context.mock(TestRemote3.class);
+
+        context.checking(new Expectations() {{
+            one(handler).doStuff("handler 1");
+        }});
+        receiver.addIncoming(TestRemote3.class, handler);
+
+        TestRemote3 remote1 = sender.addOutgoing(TestRemote3.class);
+
+        remote1.doStuff("handler 1");
+    }
+
+    @Test
+    public void cannotRegisterMultipleHandlerObjectsWithSameType() {
+        TestRemote handler = context.mock(TestRemote.class);
+        receiver.addIncoming(TestRemote.class, handler);
+
+        try {
+            receiver.addIncoming(TestRemote.class, handler);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
+        }
+    }
+
+    @Test
+    public void cannotRegisterMultipleHandlerObjectsWithOverlappingMethods() {
+        receiver.addIncoming(TestRemote3.class, context.mock(TestRemote3.class));
+
+        try {
+            receiver.addIncoming(TestRemote.class, context.mock(TestRemote.class));
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("A handler has already been added for type '" + TestRemote.class.getName() + "'."));
+        }
+    }
+
+    @Test
+    public void canCreateMultipleOutgoingObjectsWithSameType() {
+        sender.addOutgoing(TestRemote.class);
+        sender.addOutgoing(TestRemote.class);
+    }
+
+    @Test
+    public void stopsConnectionOnStop() {
+        context.checking(new Expectations() {{
+            one(stopControl).stop();
+        }});
+
+        receiver.stop();
+    }
+
+    private class TestConnection {
+        Map<Object, Dispatch<Object>> channels = new HashMap<Object, Dispatch<Object>>();
+
+        MultiChannelConnection<Object> getSender() {
+            return new MultiChannelConnection<Object>() {
+                public Dispatch<Object> addOutgoingChannel(String channelKey) {
+                    return channels.get(channelKey);
+                }
+
+                public void addIncomingChannel(String channelKey, Dispatch<Object> dispatch) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void requestStop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void stop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Address getLocalAddress() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Address getRemoteAddress() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+
+        MultiChannelConnection<Object> getReceiver() {
+            return new MultiChannelConnection<Object>() {
+                public Dispatch<Object> addOutgoingChannel(String channelKey) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void addIncomingChannel(String channelKey, Dispatch<Object> dispatch) {
+                    channels.put(channelKey, dispatch);
+                }
+
+                public void requestStop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void stop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Address getLocalAddress() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Address getRemoteAddress() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+    }
+
+    public interface TestRemote {
+        void doStuff(String param);
+    }
+
+    public interface TestRemote2 {
+        void doStuff(String param);
+    }
+
+    public interface TestRemote3 extends TestRemote {
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy
new file mode 100644
index 0000000..5dc8d70
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/DisconnectAwareConnectionDecoratorTest.groovy
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal
+
+import org.gradle.internal.concurrent.DefaultExecutorFactory
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.CountDownLatch
+
+import spock.lang.*
+import spock.util.concurrent.*
+import org.spockframework.runtime.SpockTimeoutError
+
+class DisconnectAwareConnectionDecoratorTest extends Specification {
+
+    def messageQueue = new LinkedBlockingQueue()
+
+    def rawConnection = new Connection() {
+        void stop() { disconnect() }
+        void requestStop() { disconnect() }
+        def receive() { 
+            def val = messageQueue.take().first()
+            if (val == null) {
+                messageQueue.put([null])
+            }
+            val
+        }
+        void dispatch(message) {}
+    }
+    def connection
+    
+    def connection() {
+        new DisconnectAwareConnectionDecorator(rawConnection, new DefaultExecutorFactory().create("test"))
+    }
+
+    void sendMessage(message = 1) {
+        messageQueue.put([message])
+    }
+
+    void disconnect() {
+        messageQueue.put([null])
+    }
+
+    def receive() {
+        connection.receive()
+    }
+
+    def disconnectedHolder = new BlockingVariable(3) // allow 3 seconds for the disconnect handler to fire
+
+    def onDisconnect(Closure action = { disconnectedHolder.set(true) }) {
+        connection.onDisconnect(action)
+    }
+
+    boolean isDisconnectHandlerDidFire() {
+        try {
+            disconnectedHolder.get()
+        } catch (SpockTimeoutError e) {
+            false
+        }
+    }
+
+    def setup() {
+        connection = connection()
+        onDisconnect() // install the default handler
+    }
+
+    def "normal send and receive"() {
+        when:
+        sendMessage(1)
+
+        then:
+        receive() == 1
+    }
+
+    def "disconnect after send and before message"() {
+        when:
+        sendMessage(1)
+
+        and:
+        disconnect()
+
+        then:
+        disconnectHandlerDidFire
+
+        and:
+        receive() == 1
+    }
+
+    def "disconnect before sending any messages"() {
+        when:
+        disconnect()
+
+        then:
+        disconnectHandlerDidFire
+
+        and:
+        receive() == null
+    }
+
+    def "stopping connection does not fire handler"() {
+        given:
+        sendMessage(1)
+        sendMessage(2)
+
+        when:
+        sleep 1000 // wait for the messages to be consumed by the buffer
+        connection.stop()
+
+        then:
+        receive() == 1
+        receive() == 2
+        receive() == null
+
+        and:
+        !disconnectHandlerDidFire
+    }
+
+
+    @Timeout(10)
+    def "receive does not return null until disconnect handler set and complete"() {
+        given:
+        connection = connection() // default connection has the default disconnect handler, create a new one with no handler
+        disconnect()
+        
+        def disconnectLatch = new CountDownLatch(1)
+        def receiveLatch = new CountDownLatch(1)
+        
+        and:
+        def received = []
+        Thread.start { received << receive(); receiveLatch.countDown() }
+
+        when:
+        sleep 1000
+
+        then:
+        received.empty // receive() should be blocked, waiting for the disconnect handler
+
+        when:
+        onDisconnect { disconnectLatch.await(); }
+
+        then:
+        received.empty // receive() should still be blocked because the disconnect handler hasn't completed
+
+        when:
+        disconnectLatch.countDown()
+        receiveLatch.await()
+
+        then:
+        received.size() == 1
+        received[0] == null
+    }
+
+    def cleanup() {
+        connection.stop()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy
new file mode 100644
index 0000000..1b0e927
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/EagerReceiveBufferTest.groovy
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal
+
+import org.gradle.messaging.dispatch.Receive
+import org.gradle.internal.concurrent.DefaultExecutorFactory
+
+import spock.lang.*
+
+class EagerReceiveBufferTest extends Specification {
+
+    def bufferSize = null
+    def receivers = []
+    def buffer
+
+    def receiver(Object... messages) {
+        def list = messages as LinkedList
+        receiver { list.poll() }
+    }
+
+    def receiver(Closure receiveImpl) {
+        receivers << (receiveImpl as Receive)
+    }
+
+    def executor() {
+        new DefaultExecutorFactory().create("test")
+    }
+
+    void bufferSize(int bufferSize) {
+        this.bufferSize = bufferSize
+    }
+
+    def buffer(Receive... receivers) {
+        if (bufferSize == null) {
+            new EagerReceiveBuffer(executor(), receivers as List)
+        } else {
+            new EagerReceiveBuffer(executor(), bufferSize, receivers as List)
+        }
+    }
+
+    def receive() {
+        if (buffer == null) {
+            buffer = buffer(*receivers)
+            buffer.start()
+        }
+
+        buffer.receive()
+    }
+
+    def "messages are consumed in order"() {
+        when:
+        receiver 1, 2, 3
+
+        then:
+        receive() == 1
+        receive() == 2
+        receive() == 3
+        receive() == null
+        receive() == null
+    }
+
+    def "messages are consumed from all receivers"() {
+        when:
+        receiver 1,2,3
+        receiver 4,5,6
+
+        then:
+        def messages = (1..6).collect { receive() }
+        def grouped = messages.groupBy { it < 4 ? "first" : "second" }
+        grouped.first == [1,2,3]
+        grouped.second == [4,5,6]
+    }
+
+    def "consumption blocks while the buffer is full"() {
+        given:
+        bufferSize 1
+
+        when:
+        def messages = new LinkedList([1,2,3,4])
+        receiver { messages.poll() }
+
+        then:
+        receive() == 1 // triggers consumption
+        sleep 1000 // enough time for the consumer thread to receive from our receiver, and block waiting for free buffer space
+        messages == [4] // 2 is on the buffer, 3 is being held waiting for space, 4 hasn't been received yet
+        receive() == 2
+        sleep 1000 // enough time for 3 to be put on the queue, and 4 to be received and held waiting for space
+        messages.empty
+        receive() == 3
+        receive() == 4
+    }
+
+    def "filling the buffer doesn't cause problems"() {
+        given:
+        bufferSize 1
+
+        when:
+        receiver 1,2,3
+        receiver 4,5,6
+        receiver 7,8,9
+
+        then:
+        9.times { assert receive() in 1..9; sleep 100 }
+    }
+
+    def "messages held while waiting for buffer space are discarded when stopped"() {
+        given:
+        bufferSize 1
+
+        when:
+        receiver 1,2,3,4
+
+        then:
+        receive() == 1
+        sleep 1000
+        buffer.stop()
+        receive() == 2
+        receive() == 3
+        receive() == null
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/GroupMessageFilterTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/GroupMessageFilterTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/GroupMessageFilterTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/GroupMessageFilterTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessageTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessagingServicesTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessagingServicesTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MessagingServicesTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MessagingServicesTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/PlaceholderExceptionTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ProtocolStackTest.groovy
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy
new file mode 100644
index 0000000..c2f8730
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/ReceiveProtocolTest.groovy
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal
+
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.*
+
+class ReceiveProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final UUID id = new UUID(0, 0)
+    final ReceiveProtocol protocol = new ReceiveProtocol(id, "display", "channel")
+    final UUID producer = new UUID(0, 1)
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "dispatches incoming consumer available message on start"() {
+        when:
+        protocol.start(context)
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerAvailable(id, "display", "channel"))
+        0 * context._
+    }
+
+    def "acknowledges outgoing producer ready message"() {
+        when:
+        protocol.handleIncoming(new ProducerReady(producer, id))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerReady(id, producer))
+        0 * context._
+    }
+
+    def "forwards outgoing request to consumer"() {
+        Message message = Mock()
+        def request = new Request("id", message)
+
+        when:
+        protocol.handleIncoming(request)
+
+        then:
+        1 * context.dispatchIncoming(request)
+        0 * context._
+    }
+
+    def "dispatches incoming consumer stopping to all producers on worker stop and waits for acknowledgements"() {
+        def producer1 = new UUID(0, 1)
+        def producer2 = new UUID(0, 2)
+
+        given:
+        protocol.handleIncoming(new ProducerReady(producer1, id))
+        protocol.handleIncoming(new ProducerReady(producer2, id))
+
+        when:
+        protocol.handleOutgoing(new WorkerStopping())
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerStopping(id, producer1))
+        1 * context.dispatchOutgoing(new ConsumerStopping(id, producer2))
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ProducerStopped(producer1, id))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerStopped(id, producer1))
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ProducerStopped(producer2, id))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerStopped(id, producer2))
+        1 * context.dispatchOutgoing(new ConsumerUnavailable(id))
+
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+    }
+
+    def "acknowledges outgoing producer stopped message"() {
+        given:
+        protocol.handleIncoming(new ProducerReady(producer, id))
+
+        when:
+        protocol.handleIncoming(new ProducerStopped(producer, id))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerStopped(id, producer))
+        0 * context._
+    }
+
+    def "worker stop does not dispatch consumer stopping to producer which has stopped"() {
+        given:
+        protocol.handleIncoming(new ProducerReady(producer, id))
+        protocol.handleIncoming(new ProducerStopped(producer, id))
+
+        when:
+        protocol.handleOutgoing(new WorkerStopping())
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerUnavailable(id))
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+    }
+
+    def "worker stop does not dispatch consumer stopping to producer which becomes unavailable"() {
+        given:
+        protocol.handleIncoming(new ProducerReady(producer, id))
+        protocol.handleIncoming(new ProducerUnavailable(producer))
+
+        when:
+        protocol.handleOutgoing(new WorkerStopping())
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerUnavailable(id))
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+    }
+
+    def "worker stop does not wait for producer which becomes unavailable during stop"() {
+        given:
+        protocol.handleIncoming(new ProducerReady(producer, id))
+        protocol.handleOutgoing(new WorkerStopping())
+
+        when:
+        protocol.handleIncoming(new ProducerUnavailable(producer))
+
+        then:
+        1 * context.dispatchOutgoing(new ConsumerUnavailable(id))
+        1 * context.dispatchIncoming(new EndOfStreamEvent())
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocolTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocolTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/RemoteDisconnectProtocolTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RouterTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/RouterTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/RouterTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/RouterTest.groovy
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy
new file mode 100644
index 0000000..4bb0d1d
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/SendProtocolTest.groovy
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal
+
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.*
+
+class SendProtocolTest extends Specification {
+    final ProtocolContext<Object> context = Mock()
+    final UUID id = new UUID(0, 1)
+    final SendProtocol protocol = new SendProtocol(id, "display", "channel")
+    final UUID consumer = new UUID(0, 2)
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "dispatches outgoing producer available message on start"() {
+        when:
+        protocol.start(context)
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerAvailable(id, "display", "channel"))
+        0 * context._
+    }
+
+    def "dispatches outgoing producer ready when incoming consumer available received"() {
+        when:
+        protocol.handleIncoming(new ConsumerAvailable(consumer, "consumer-display", "channel"))
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerReady(id, consumer))
+        0 * context._
+    }
+
+    def "dispatches incoming consumer available when consumer ready received"() {
+        def available = new ConsumerAvailable(consumer, "display", "channel")
+
+        given:
+        protocol.handleIncoming(available)
+
+        when:
+        protocol.handleIncoming(new ConsumerReady(consumer, id))
+
+        then:
+        1 * context.dispatchIncoming(available)
+        0 * context._
+    }
+
+    def "dispatches incoming consumer unavailable and outgoing producer stopped when consumer stopping received"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(consumer, "display", "channel"))
+
+        when:
+        protocol.handleIncoming(new ConsumerStopping(consumer, id))
+
+        then:
+        1 * context.dispatchIncoming(new ConsumerUnavailable(consumer))
+        1 * context.dispatchOutgoing(new ProducerStopped(id, consumer))
+        0 * context._
+    }
+
+    def "stop dispatches outgoing producer stopped to all consumers and waits for acknowledgement"() {
+        def consumer1 = new UUID(0, 1)
+        def consumer2 = new UUID(0, 2)
+
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(consumer1, "display", "channel"))
+        protocol.handleIncoming(new ConsumerReady(consumer1, id))
+        protocol.handleIncoming(new ConsumerAvailable(consumer2, "display", "channel"))
+        protocol.handleIncoming(new ConsumerReady(consumer2, id))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerStopped(id, consumer1))
+        1 * context.dispatchOutgoing(new ProducerStopped(id, consumer2))
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerStopped(consumer1, id))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerStopped(consumer2, id))
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable(id))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops when no consumers"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable(id))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "does not dispatch stopped message to consumer which has stopped"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(consumer, "display", "channel"))
+        protocol.handleIncoming(new ConsumerStopping(consumer, id))
+        protocol.handleIncoming(new ConsumerStopped(consumer, id))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable(id))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "handles consumer which becomes unavailable while waiting for consumer ready"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(consumer, "display", "channel"))
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable(consumer))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable(id))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "handles consumer which becomes unavailable while waiting for consumer stopped"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(consumer, "display", "channel"))
+        protocol.handleIncoming(new ConsumerReady(consumer, id))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerStopped(id, consumer))
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable(consumer))
+
+        then:
+        1 * context.dispatchIncoming(new ConsumerUnavailable(consumer))
+        1 * context.dispatchOutgoing(new ProducerUnavailable(id))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "handles consumer which becomes unavailable without consumer stopping message received"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(consumer, "display", "channel"))
+        protocol.handleIncoming(new ConsumerReady(consumer, id))
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable(consumer))
+
+        then:
+        1 * context.dispatchIncoming(new ConsumerUnavailable(consumer))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.dispatchOutgoing(new ProducerUnavailable(id))
+        1 * context.stopped()
+        0 * context._
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy
new file mode 100644
index 0000000..c2cc5e3
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/UnicastSendProtocolTest.groovy
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal
+
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.protocol.ConsumerAvailable
+import org.gradle.messaging.remote.internal.protocol.Request
+import org.gradle.messaging.remote.internal.protocol.ConsumerUnavailable
+
+class UnicastSendProtocolTest extends Specification {
+    final ProtocolContext<Message> context = Mock()
+    final UnicastSendProtocol protocol = new UnicastSendProtocol()
+    final UUID id = new UUID(0, 1)
+
+    def setup() {
+        protocol.start(context)
+    }
+
+    def "queues outgoing messages until a consumer is available"() {
+        when:
+        protocol.handleOutgoing(new Request("channel", "message1"))
+        protocol.handleOutgoing(new Request("channel", "message2"))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request(id, "message1"))
+        1 * context.dispatchOutgoing(new Request(id, "message2"))
+        0 * context._
+    }
+
+    def "forwards messages when a consumer is available"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+
+        when:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request(id, "message"))
+    }
+
+    def "stop waits until a consumer is available and messages dispatched"() {
+        given:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopLater()
+        0 * context._
+
+        when:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channnel"))
+
+        then:
+        1 * context.dispatchOutgoing(new Request(id, "message"))
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when no messages have been dispatched"() {
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stops immediately when all messages have been dispatched"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "discards messages after consumer becomes unavailable"() {
+        given:
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+        protocol.handleIncoming(new ConsumerUnavailable(id))
+
+        when:
+        protocol.handleOutgoing(new Request("channel", "message"))
+
+        then:
+        0 * context._
+
+        when:
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+
+    def "stop ignores consumer unavailable when everything dispatched"() {
+        given:
+        protocol.handleOutgoing(new Request("channel", "message"))
+        protocol.handleIncoming(new ConsumerAvailable(id, "display", "channel"))
+
+        when:
+        protocol.handleIncoming(new ConsumerUnavailable(id))
+        protocol.stopRequested()
+
+        then:
+        1 * context.stopped()
+        0 * context._
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/WorkerProtocolTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/WorkerProtocolTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/WorkerProtocolTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/WorkerProtocolTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddressTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddressTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddressTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/MultiChoiceAddressTest.groovy
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddressTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddressTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddressTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/SocketInetAddressTest.groovy
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
new file mode 100644
index 0000000..b88f495
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2009 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 org.gradle.messaging.remote.internal.inet
+
+import java.util.concurrent.atomic.AtomicInteger
+import org.gradle.api.Action
+import org.gradle.internal.id.UUIDGenerator
+import org.gradle.messaging.remote.internal.DefaultMessageSerializer
+import org.gradle.util.ConcurrentSpecification
+import org.slf4j.LoggerFactory
+import spock.lang.Ignore
+import spock.lang.Timeout
+import static java.util.Collections.synchronizedList
+
+class TcpConnectorConcurrencyTest extends ConcurrentSpecification {
+
+    final static LOGGER = LoggerFactory.getLogger(TcpConnectorConcurrencyTest)
+
+    //sharing serializer adds extra flavor...
+    final serializer = new DefaultMessageSerializer<Object>(getClass().classLoader)
+    final outgoingConnector = new TcpOutgoingConnector<Object>(serializer)
+    final incomingConnector = new TcpIncomingConnector<Object>(executorFactory, serializer, new InetAddressFactory(), new UUIDGenerator())
+
+    @Timeout(60)
+    @Ignore
+    //TODO SF exposes concurrency issue
+    def "can dispatch from multiple threads"() {
+        def number = new AtomicInteger(1)
+        def threads = 20
+        def messages = synchronizedList([])
+
+        Action action = new Action() {
+            void execute(event) {
+                while (true) {
+                    def message = event.connection.receive()
+                    LOGGER.debug("*** received: $message")
+                    messages << message
+                    if (messages.size() == threads || message == null) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        def address = incomingConnector.accept(action, false)
+        def connection = outgoingConnector.connect(address)
+
+        when:
+        def all = []
+        threads.times {
+            all << start {
+                //exceptions carry lots of information so serialization/deserialization is slower
+                //and hence better chance of reproducing the concurrency bugs
+                def message = new RuntimeException("Message #" + number.getAndIncrement())
+                connection.dispatch(message)
+                LOGGER.debug("*** dispatched: $message")
+            }
+        }
+
+        all*.completed()
+
+        then:
+        //let's give some time for the messages to arrive to the sink
+        poll(20) {
+            messages.size() == threads
+            messages.each { it.toString().contains("Message #") }
+        }
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
+}
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
new file mode 100644
index 0000000..a2c289a
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2010 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 org.gradle.messaging.remote.internal.inet
+
+import org.gradle.api.Action
+import org.gradle.messaging.remote.internal.ConnectException
+import org.gradle.messaging.remote.internal.DefaultMessageSerializer
+import org.gradle.util.ConcurrentSpecification
+import org.gradle.internal.id.UUIDGenerator
+
+class TcpConnectorTest extends ConcurrentSpecification {
+    final def serializer = new DefaultMessageSerializer<String>(getClass().classLoader)
+    final def idGenerator = new UUIDGenerator()
+    final def addressFactory = new InetAddressFactory()
+    final def outgoingConnector = new TcpOutgoingConnector<String>(serializer)
+    final def incomingConnector = new TcpIncomingConnector<String>(executorFactory, serializer, addressFactory, idGenerator)
+
+    def "client can connect to server"() {
+        Action action = Mock()
+
+        when:
+        def address = incomingConnector.accept(action, false)
+        def connection = outgoingConnector.connect(address)
+
+        then:
+        connection != null
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
+
+    def "client can connect to server using remote addresses"() {
+        Action action = Mock()
+
+        when:
+        def address = incomingConnector.accept(action, true)
+        def connection = outgoingConnector.connect(address)
+
+        then:
+        connection != null
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
+
+    def "server executes action when incoming connection received"() {
+        def connectionReceived = startsAsyncAction()
+        Action action = Mock()
+
+        when:
+        connectionReceived.started {
+            def address = incomingConnector.accept(action, false)
+            outgoingConnector.connect(address)
+        }
+
+        then:
+        1 * action.execute(!null) >> { connectionReceived.done() }
+
+        cleanup:
+        incomingConnector.requestStop()
+    }
+
+    def "client throws exception when cannot connect to server"() {
+        def address = new MultiChoiceAddress("address", 12345, [InetAddress.getByName("localhost")])
+
+        when:
+        outgoingConnector.connect(address)
+
+        then:
+        ConnectException e = thrown()
+        e.message.startsWith "Could not connect to server ${address}."
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessageTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessageTest.groovy
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessageTest.groovy
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/AbstractPayloadMessageTest.groovy
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
new file mode 100644
index 0000000..bcdf1f8
--- /dev/null
+++ b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/DiscoveryProcotolSerializerTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2011 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 org.gradle.messaging.remote.internal.protocol
+
+import org.gradle.messaging.remote.internal.inet.MultiChoiceAddress
+import org.gradle.internal.id.UUIDGenerator
+import spock.lang.Shared
+import spock.lang.Specification
+import org.gradle.messaging.remote.internal.MessageOriginator
+import org.gradle.messaging.remote.internal.inet.SocketInetAddress
+
+class DiscoveryProcotolSerializerTest extends Specification {
+    final DiscoveryProtocolSerializer serializer = new DiscoveryProtocolSerializer()
+    @Shared def uuidGenerator = new UUIDGenerator()
+    @Shared MessageOriginator messageOriginator = new MessageOriginator(uuidGenerator.generateId(), "source display name")
+    @Shared InetAddress address = InetAddress.getByName(null)
+    final InetAddress receivedAddress = Mock()
+
+    def "writes and reads message types"() {
+        when:
+        def result = send(original)
+
+        then:
+        result == original
+
+        where:
+        original << [
+                new LookupRequest(messageOriginator, "group", "channel"),
+                new ChannelUnavailable(messageOriginator, "group", "channel", new MultiChoiceAddress(UUID.randomUUID(), 8091, [address]))
+        ]
+    }
+
+    def "mixes in remote address to received ChannelAvailable message"() {
+        def originatorId = UUID.randomUUID()
+        def original = new ChannelAvailable(messageOriginator, "group", "channel", new MultiChoiceAddress(originatorId, 8091, [address]))
+        def expected = new ChannelAvailable(messageOriginator, "group", "channel", new MultiChoiceAddress(originatorId, 8091, [receivedAddress, address]))
+
+        when:
+        def result = send(original)
+
+        then:
+        result == expected
+    }
+
+    def "can read message for unknown protocol version"() {
+        expect:
+        def result = send { outstr ->
+            outstr.writeByte(90)
+        }
+        result instanceof UnknownMessage
+        result.toString() == "unknown protocol version 90"
+    }
+
+    def "can read unknown message type"() {
+        expect:
+        def result = send { outstr ->
+            outstr.writeByte(DiscoveryProtocolSerializer.PROTOCOL_VERSION);
+            outstr.writeByte(90)
+        }
+        result instanceof UnknownMessage
+        result.toString() == "unknown message type 90"
+    }
+
+    def send(Closure cl) {
+        def bytesOut = new ByteArrayOutputStream()
+        def outstr = new DataOutputStream(bytesOut)
+        cl.call(outstr)
+        outstr.close()
+
+        def bytesIn = new ByteArrayInputStream(bytesOut.toByteArray())
+        return serializer.read(new DataInputStream(bytesIn), null, new SocketInetAddress(receivedAddress, 9122))
+    }
+
+    def send(DiscoveryMessage message) {
+        def bytesOut = new ByteArrayOutputStream()
+        def outstr = new DataOutputStream(bytesOut)
+        serializer.write(message, outstr)
+        outstr.close()
+
+        def bytesIn = new ByteArrayInputStream(bytesOut.toByteArray())
+        return serializer.read(new DataInputStream(bytesIn), null, new SocketInetAddress(receivedAddress, 9122))
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocationTest.java b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocationTest.java
similarity index 100%
rename from subprojects/core/src/test/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocationTest.java
rename to subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/protocol/RemoteMethodInvocationTest.java
diff --git a/subprojects/migration/migration.gradle b/subprojects/migration/migration.gradle
new file mode 100644
index 0000000..eab7c09
--- /dev/null
+++ b/subprojects/migration/migration.gradle
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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.
+ */
+
+dependencies {
+    groovy libraries.groovy
+
+    compile project(":core")
+    compile project(":toolingApi")
+    compile libraries.guava
+    compile libraries.slf4j_api
+}
+
+//useTestFixtures()
diff --git a/subprojects/native/native.gradle b/subprojects/native/native.gradle
index 1778ca8..e409e5c 100755
--- a/subprojects/native/native.gradle
+++ b/subprojects/native/native.gradle
@@ -20,6 +20,7 @@ dependencies {
     compile libraries.jcip
 }
 
+// TODO SF, replace with JavaVersion when 1.1 rc is building gradle
 if (!Jvm.current().isJava7()) {
     sourceSets.main.java.exclude '**/jdk7/**'
     sourceSets.test.groovy.exclude '**/jdk7/**'
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Chmod.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Chmod.java
new file mode 100644
index 0000000..bfa8069
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Chmod.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface Chmod {
+    public void chmod(File f, int mode) throws IOException;
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandler.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandler.java
deleted file mode 100644
index e059430..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandler.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
-
-import org.jruby.ext.posix.POSIX;
-
-import java.io.File;
-import java.io.IOException;
-
-public class ComposableFilePermissionHandler implements FilePermissionHandler {
-    private final Chmod chmod;
-    private final POSIX posix;
-
-    public ComposableFilePermissionHandler(Chmod chmod, POSIX posix) {
-        this.chmod = chmod;
-        this.posix = posix;
-    }
-
-    public int getUnixMode(File f) throws IOException {
-        return posix.stat(f.getAbsolutePath()).mode() & 0777;
-    }
-
-    public void chmod(File f, int mode) throws IOException {
-        chmod.chmod(f, mode);
-    }
-
-    public static interface Chmod {
-        public void chmod(File f, int mode) throws IOException;
-
-    }
-}
\ No newline at end of file
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/DefaultFilePathEncoder.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/DefaultFilePathEncoder.java
new file mode 100644
index 0000000..35be6a4
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/DefaultFilePathEncoder.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import com.sun.jna.WString;
+import org.gradle.internal.nativeplatform.jna.LibC;
+
+import java.io.File;
+
+class DefaultFilePathEncoder implements FilePathEncoder {
+    private final LibC libC;
+
+    DefaultFilePathEncoder(LibC libC) {
+        this.libC = libC;
+    }
+
+    public byte[] encode(File file) {
+        byte[] path = new byte[file.getAbsolutePath().length() * 3 + 1];
+        int pathLength = libC.wcstombs(path, new WString(file.getAbsolutePath()), path.length);
+        if (pathLength < 0) {
+            throw new RuntimeException(String.format("Could not encode file path '%s'.", file.getAbsolutePath()));
+        }
+        byte[] zeroTerminatedByteArray = new byte[pathLength + 1];
+        System.arraycopy(path, 0, zeroTerminatedByteArray, 0, pathLength);
+        zeroTerminatedByteArray[pathLength] = 0;
+        return zeroTerminatedByteArray;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/EmptyChmod.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/EmptyChmod.java
new file mode 100644
index 0000000..65bc53c
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/EmptyChmod.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+
+class EmptyChmod implements Chmod {
+    public void chmod(File f, int mode) throws IOException {
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackFileStat.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackFileStat.java
deleted file mode 100644
index 43d79c6..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackFileStat.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
-
-import org.jruby.ext.posix.FileStat;
-
-import java.io.File;
-
-public class FallbackFileStat implements FileStat {
-
-    private final File file;
-
-    public FallbackFileStat(String path) {
-        this.file = new File(path);
-    }
-
-    public long atime() {
-        throw new UnsupportedOperationException("Operation atime() is not supported.");
-    }
-
-    public long blocks() {
-        throw new UnsupportedOperationException("Operation blocks is not supported.");
-    }
-
-    public long blockSize() {
-        throw new UnsupportedOperationException("Operation blockSize() is not supported.");
-    }
-
-    public long ctime() {
-        throw new UnsupportedOperationException("Operation ctime() is not supported.");
-    }
-
-    public long dev() {
-        throw new UnsupportedOperationException("Operation dev() is not supported.");
-    }
-
-    public String ftype() {
-        throw new UnsupportedOperationException("Operation ftype() is not supported.");
-    }
-
-    public int gid() {
-        throw new UnsupportedOperationException("Operation gid() is not supported.");
-    }
-
-    public boolean groupMember(int gid) {
-        throw new UnsupportedOperationException("Operation groupMember() is not supported.");
-    }
-
-    public long ino() {
-        throw new UnsupportedOperationException("Operation ino() is not supported.");
-    }
-
-    public boolean isBlockDev() {
-        throw new UnsupportedOperationException("Operation isBlockDev() is not supported.");
-    }
-
-    public boolean isCharDev() {
-        throw new UnsupportedOperationException("Operation isCharDev() is not supported.");
-    }
-
-    public boolean isDirectory() {
-        return file.isDirectory();
-    }
-
-    public boolean isEmpty() {
-        throw new UnsupportedOperationException("Operation isEmpty() is not supported.");
-    }
-
-    public boolean isExecutable() {
-        throw new UnsupportedOperationException("Operation isExecutable() is not supported.");
-    }
-
-    public boolean isExecutableReal() {
-        throw new UnsupportedOperationException("Operation isExecutableReal() is not supported.");
-    }
-
-    public boolean isFifo() {
-        throw new UnsupportedOperationException("Operation isFifo() is not supported.");
-    }
-
-    public boolean isFile() {
-        return file.isFile();
-    }
-
-    public boolean isGroupOwned() {
-        throw new UnsupportedOperationException("Operation isGroupOwned() is not supported.");
-    }
-
-    public boolean isIdentical(FileStat other) {
-        throw new UnsupportedOperationException("Operation isIdentical() is not supported.");
-    }
-
-    public boolean isNamedPipe() {
-        throw new UnsupportedOperationException("Operation isNamedPipe() is not supported.");
-    }
-
-    public boolean isOwned() {
-        throw new UnsupportedOperationException("Operation isOwned() is not supported.");
-    }
-
-    public boolean isROwned() {
-        throw new UnsupportedOperationException("Operation isROwned() is not supported.");
-    }
-
-    public boolean isReadable() {
-        throw new UnsupportedOperationException("Operation isReadable() is not supported.");
-    }
-
-    public boolean isReadableReal() {
-        throw new UnsupportedOperationException("Operation isReadableReal() is not supported.");
-    }
-
-    public boolean isWritable() {
-        throw new UnsupportedOperationException("Operation isWritable() is not supported.");
-    }
-
-    public boolean isWritableReal() {
-        throw new UnsupportedOperationException("Operation isWritableReal() is not supported.");
-    }
-
-    public boolean isSetgid() {
-        throw new UnsupportedOperationException("Operation isSetgid() is not supported.");
-    }
-
-    public boolean isSetuid() {
-        throw new UnsupportedOperationException("Operation isSetuid() is not supported.");
-    }
-
-    public boolean isSocket() {
-        throw new UnsupportedOperationException("Operation isSocket() is not supported.");
-    }
-
-    public boolean isSticky() {
-        throw new UnsupportedOperationException("Operation isSticky() is not supported.");
-    }
-
-    public boolean isSymlink() {
-        throw new UnsupportedOperationException("Operation isSymlink() is not supported.");
-    }
-
-    public int major(long dev) {
-        throw new UnsupportedOperationException("Operation major() is not supported.");
-    }
-
-    public int minor(long dev) {
-        throw new UnsupportedOperationException("Operation minor() is not supported.");
-    }
-
-    public int mode() {
-        if (isDirectory()) {
-            return FileSystem.DEFAULT_DIR_MODE;
-        } else {
-            return FileSystem.DEFAULT_FILE_MODE;
-        }
-    }
-
-    public long mtime() {
-        throw new UnsupportedOperationException("Operation mtime() is not supported.");
-    }
-
-    public int nlink() {
-        throw new UnsupportedOperationException("Operation nlink() is not supported.");
-    }
-
-    public long rdev() {
-        throw new UnsupportedOperationException("Operation rdev() is not supported.");
-    }
-
-    public long st_size() {
-        throw new UnsupportedOperationException("Operation st_size() is not supported.");
-    }
-
-    public int uid() {
-        throw new UnsupportedOperationException("Operation uid() is not supported.");
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIX.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIX.java
deleted file mode 100644
index 45bd2cf..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIX.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
-
-import org.jruby.ext.posix.FileStat;
-import org.jruby.ext.posix.Group;
-import org.jruby.ext.posix.POSIX;
-import org.jruby.ext.posix.Passwd;
-
-import java.io.FileDescriptor;
-import java.io.IOException;
-
-public class FallbackPOSIX implements POSIX {
-
-    static final int ENOTSUP = 1;
-
-    public int chmod(String filename, int mode) {
-        return 0;
-    }
-
-    public int chown(String filename, int user, int group) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int fork() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public FileStat fstat(FileDescriptor descriptor) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getegid() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int geteuid() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int seteuid(int euid) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getgid() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public String getlogin() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getpgid() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getpgid(int pid) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getpgrp() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getpid() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getppid() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getpriority(int which, int who) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public Passwd getpwent() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public Passwd getpwuid(int which) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public Passwd getpwnam(String which) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public Group getgrgid(int which) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public Group getgrnam(String which) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public Group getgrent() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int endgrent() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setgrent() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int endpwent() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setpwent() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int getuid() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public boolean isatty(FileDescriptor descriptor) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int kill(int pid, int signal) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int lchmod(String filename, int mode) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int lchown(String filename, int user, int group) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int link(String oldpath, String newpath) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public FileStat lstat(String path) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int mkdir(String path, int mode) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public String readlink(String path) throws IOException {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setsid() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setgid(int gid) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setegid(int egid) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setpgid(int pid, int pgid) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setpgrp(int pid, int pgrp) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setpriority(int which, int who, int prio) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int setuid(int uid) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public FileStat stat(String path) {
-        return new FallbackFileStat(path);
-    }
-
-    public int symlink(String oldpath, String newpath) {
-        return ENOTSUP;
-    }
-
-    public int umask(int mask) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int utimes(String path, long[] atimeval, long[] mtimeval) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int waitpid(int pid, int[] status, int flags) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int wait(int[] status) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public int errno() {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-
-    public void errno(int value) {
-        throw new UnsupportedOperationException("This operation is not supported.");
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackStat.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackStat.java
new file mode 100644
index 0000000..1108039
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackStat.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+
+class FallbackStat implements Stat {
+    public int getUnixMode(File f) throws IOException {
+        if (f.isDirectory()) {
+            return FileSystem.DEFAULT_DIR_MODE;
+        } else {
+            return FileSystem.DEFAULT_FILE_MODE;
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackSymlink.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackSymlink.java
new file mode 100644
index 0000000..053ba76
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FallbackSymlink.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FallbackSymlink implements Symlink {
+    public void symlink(File link, File target) throws IOException {
+        throw new IOException("Creation of symlinks is not supported on the platform.");
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePathEncoder.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePathEncoder.java
new file mode 100644
index 0000000..b671c20
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePathEncoder.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import java.io.File;
+
+interface FilePathEncoder {
+    byte[] encode(File file);
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandler.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandler.java
deleted file mode 100644
index 2f35a2c..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandler.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
-
-import java.io.File;
-import java.io.IOException;
-
-public interface FilePermissionHandler {
-    public int getUnixMode(File f) throws IOException;
-    public void chmod(File f, int mode) throws IOException;
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactory.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactory.java
deleted file mode 100644
index 99e40a5..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactory.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
-
-import com.sun.jna.LastErrorException;
-import com.sun.jna.Native;
-import org.gradle.internal.jvm.Jvm;
-import org.gradle.internal.nativeplatform.jna.LibC;
-import org.gradle.internal.os.OperatingSystem;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.IOException;
-
-public class FilePermissionHandlerFactory {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(FilePermissionHandlerFactory.class);
-
-    public static FilePermissionHandler createDefaultFilePermissionHandler() {
-        if (Jvm.current().isJava7() && !OperatingSystem.current().isWindows()) {
-            try {
-                String jdkFilePermissionclass = "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler";
-                FilePermissionHandler jdk7FilePermissionHandler =
-                        (FilePermissionHandler) FilePermissionHandler.class.getClassLoader().loadClass(jdkFilePermissionclass).newInstance();
-                return jdk7FilePermissionHandler;
-            } catch (Exception e) {
-                LOGGER.warn("Unable to load Jdk7FilePermissionHandler", e);
-            }
-        }
-        ComposableFilePermissionHandler.Chmod chmod = createChmod();
-        return new ComposableFilePermissionHandler(chmod, PosixUtil.current());
-    }
-
-    static ComposableFilePermissionHandler.Chmod createChmod() {
-        try {
-            LibC libc = (LibC) Native.loadLibrary("c", LibC.class);
-            return new LibcChmod(libc);
-        } catch (LinkageError e) {
-            return new EmptyChmod();
-        }
-    }
-
-    private static class LibcChmod implements ComposableFilePermissionHandler.Chmod {
-        private final LibC libc;
-
-        public LibcChmod(LibC libc) {
-            this.libc = libc;
-        }
-
-        public void chmod(File f, int mode) throws IOException {
-            try{
-                libc.chmod(f.getAbsolutePath(), mode);
-            }catch(LastErrorException exception){
-                throw new IOException(String.format("Failed to set file permissions %s on file %s. errno: %d", mode, f.getName(), exception.getErrorCode()));
-            }
-        }
-    }
-
-    private static class EmptyChmod implements ComposableFilePermissionHandler.Chmod {
-        public void chmod(File f, int mode) throws IOException {
-        }
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystemServices.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystemServices.java
new file mode 100644
index 0000000..f37472d
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystemServices.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import com.sun.jna.Native;
+import org.gradle.api.JavaVersion;
+import org.gradle.internal.UncheckedException;
+import org.gradle.internal.nativeplatform.jna.LibC;
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.internal.service.DefaultServiceRegistry;
+import org.gradle.internal.service.ServiceRegistry;
+import org.jruby.ext.posix.BaseNativePOSIX;
+import org.jruby.ext.posix.JavaPOSIX;
+import org.jruby.ext.posix.POSIX;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FileSystemServices {
+    private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemServices.class);
+    private static final ServiceRegistry SERVICES;
+
+    static {
+        DefaultServiceRegistry serviceRegistry = new DefaultServiceRegistry();
+        addServices(serviceRegistry);
+        SERVICES = serviceRegistry;
+    }
+
+    public static ServiceRegistry getServices() {
+        return SERVICES;
+    }
+
+    private static void addServices(DefaultServiceRegistry serviceRegistry) {
+        OperatingSystem operatingSystem = OperatingSystem.current();
+
+        // Use no-op implementations for windows
+        if (operatingSystem.isWindows()) {
+            serviceRegistry.add(Chmod.class, new EmptyChmod());
+            serviceRegistry.add(Stat.class, new FallbackStat());
+            serviceRegistry.add(Symlink.class, new FallbackSymlink());
+            return;
+        }
+
+        LibC libC = loadLibC();
+        serviceRegistry.add(Symlink.class, createSymlink(libC));
+
+        // Use libc backed implementations on Linux and Mac
+        if (operatingSystem.isLinux() || operatingSystem.isMacOsX()) {
+            FilePathEncoder filePathEncoder = createEncoder(libC);
+            serviceRegistry.add(Chmod.class, new LibcChmod(libC, filePathEncoder));
+            serviceRegistry.add(Stat.class, new LibCStat(libC, operatingSystem, (BaseNativePOSIX) PosixUtil.current(), filePathEncoder));
+            return;
+        }
+
+        // Use java 7 APIs, if available
+        if (JavaVersion.current().isJava7()) {
+            String jdkFilePermissionclass = "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler";
+            try {
+                Object handler = FileSystemServices.class.getClassLoader().loadClass(jdkFilePermissionclass).newInstance();
+                serviceRegistry.add(Stat.class, (Stat) handler);
+                serviceRegistry.add(Chmod.class, (Chmod) handler);
+                return;
+            } catch (ClassNotFoundException e) {
+                LOGGER.warn(String.format("Unable to load %s. Continuing with fallback.", jdkFilePermissionclass));
+            } catch (Exception e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
+        // Not windows, linux, mac or java 7. Attempt to use libc for chmod and posix for stat, and fallback to no-op implementations if not available
+        serviceRegistry.add(Chmod.class, createChmod(libC));
+        serviceRegistry.add(Stat.class, createStat());
+    }
+
+    private static Symlink createSymlink(LibC libC) {
+        if (libC != null) {
+            return new LibcSymlink(libC);
+        }
+        LOGGER.debug("Using FallbackSymlink implementation.");
+        return new FallbackSymlink();
+    }
+
+    private static Stat createStat() {
+        POSIX posix = PosixUtil.current();
+        if (posix instanceof JavaPOSIX) {
+            return new FallbackStat();
+        } else {
+            return new PosixStat(posix);
+        }
+    }
+
+    static Chmod createChmod(LibC libC) {
+        if (libC != null) {
+            return new LibcChmod(libC, createEncoder(libC));
+        }
+        LOGGER.debug("Using EmptyChmod implementation.");
+        return new EmptyChmod();
+    }
+
+    static FilePathEncoder createEncoder(LibC libC) {
+        if (OperatingSystem.current().isMacOsX()) {
+            return new MacFilePathEncoder();
+        }
+        return new DefaultFilePathEncoder(libC);
+    }
+
+    private static LibC loadLibC() {
+        try {
+            return (LibC) Native.loadLibrary("c", LibC.class);
+        } catch (LinkageError e) {
+            LOGGER.debug("Unable to load LibC library. Continuing with fallback filesystem implementations.");
+            return null;
+        }
+    }
+
+}
+
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java
index ee158b1..a9292fe 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/FileSystems.java
@@ -15,12 +15,19 @@
  */
 package org.gradle.internal.nativeplatform.filesystem;
 
+import org.gradle.internal.service.ServiceRegistry;
+
 public abstract class FileSystems {
     public static FileSystem getDefault() {
         return DefaultFileSystem.INSTANCE;
     }
 
     private static class DefaultFileSystem {
-        static final FileSystem INSTANCE = new GenericFileSystem(FilePermissionHandlerFactory.createDefaultFilePermissionHandler());
+        static final FileSystem INSTANCE;
+
+        static {
+            ServiceRegistry services = FileSystemServices.getServices();
+            INSTANCE = new GenericFileSystem(services.get(Chmod.class), services.get(Stat.class), services.get(Symlink.class));
+        }
     }
 }
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java
index 236630c..445a346 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/GenericFileSystem.java
@@ -32,7 +32,9 @@ class GenericFileSystem implements FileSystem {
     final boolean caseSensitive;
     final boolean canCreateSymbolicLink;
 
-    final FilePermissionHandler filePermissionHandler;
+    private final Chmod chmod;
+    private final Stat stat;
+    private final Symlink symlink;
 
     public boolean isCaseSensitive() {
         return caseSensitive;
@@ -43,35 +45,26 @@ class GenericFileSystem implements FileSystem {
     }
 
     public void createSymbolicLink(File link, File target) throws IOException {
-        int returnCode = doCreateSymbolicLink(link, target);
-        if (returnCode != 0) {
-            throw new IOException("Failed to create symbolic link " + link
-                    + " pointing to " + target + ". Return code is: " + returnCode);
-        }
+        symlink.symlink(link, target);
     }
 
     public boolean tryCreateSymbolicLink(File link, File target) {
-        return doCreateSymbolicLink(link, target) == 0;
+        try {
+            symlink.symlink(link, target);
+            return true;
+        } catch (IOException e) {
+            return false;
+        }
     }
 
     public int getUnixMode(File f) throws IOException {
         assertFileExists(f);
-        return filePermissionHandler.getUnixMode(f);
+        return stat.getUnixMode(f);
     }
 
     public void chmod(File f, int mode) throws IOException {
         assertFileExists(f);
-        filePermissionHandler.chmod(f, mode);
-    }
-
-    private int doCreateSymbolicLink(File link, File target) {
-        link.getParentFile().mkdirs();
-        try {
-            return PosixUtil.current().symlink(target.getPath(), link.getPath());
-        } catch (UnsatisfiedLinkError e) {
-            // Assume symlink() is not available
-            return 1;
-        }
+        chmod.chmod(f, mode);
     }
 
     protected final void assertFileExists(File f) throws FileNotFoundException {
@@ -80,7 +73,10 @@ class GenericFileSystem implements FileSystem {
         }
     }
 
-    GenericFileSystem(FilePermissionHandler handler) {
+    GenericFileSystem(Chmod chmod, Stat stat, Symlink symlink) {
+        this.stat = stat;
+        this.symlink = symlink;
+        this.chmod = chmod;
         String content = generateUniqueContent();
         File file = null;
         try {
@@ -88,7 +84,6 @@ class GenericFileSystem implements FileSystem {
             file = createFile(content);
             caseSensitive = probeCaseSensitive(file, content);
             canCreateSymbolicLink = probeCanCreateSymbolicLink(file, content);
-            filePermissionHandler = handler;
         } catch (IOException e) {
             throw new RuntimeException(e);
         } finally {
@@ -123,8 +118,7 @@ class GenericFileSystem implements FileSystem {
         File link = null;
         try {
             link = generateUniqueTempFileName();
-            int returnCode = doCreateSymbolicLink(link, file);
-            return returnCode == 0 && hasContent(link, content);
+            return tryCreateSymbolicLink(link, file) && hasContent(link, content);
         } catch (IOException e) {
             LOGGER.info("Failed to determine if file system can create symbolic links. Assuming it can't.");
             return false;
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibCStat.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibCStat.java
new file mode 100644
index 0000000..a9ad76c
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibCStat.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import org.gradle.internal.nativeplatform.jna.LibC;
+import org.gradle.internal.os.OperatingSystem;
+import org.jruby.ext.posix.BaseNativePOSIX;
+import org.jruby.ext.posix.FileStat;
+import org.jruby.ext.posix.Linux64FileStat;
+
+import java.io.File;
+import java.io.IOException;
+
+class LibCStat implements Stat {
+    private final LibC libc;
+    private final FilePathEncoder encoder;
+    private final OperatingSystem operatingSystem;
+    private final BaseNativePOSIX nativePOSIX;
+
+    public LibCStat(LibC libc, OperatingSystem operatingSystem, BaseNativePOSIX nativePOSIX, FilePathEncoder encoder) {
+        this.libc = libc;
+        this.operatingSystem = operatingSystem;
+        this.nativePOSIX = nativePOSIX;
+        this.encoder = encoder;
+    }
+
+    public int getUnixMode(File f) throws IOException {
+        FileStat stat = nativePOSIX.allocateStat();
+        initPlatformSpecificStat(stat, encoder.encode(f));
+        return stat.mode() & 0777;
+    }
+
+    private void initPlatformSpecificStat(FileStat stat, byte[] encodedFilePath) {
+        if (operatingSystem.isMacOsX()) {
+            libc.stat(encodedFilePath, stat);
+        } else {
+            final int statVersion = stat instanceof Linux64FileStat ? 3 : 0;
+            libc.__xstat64(statVersion, encodedFilePath, stat);
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibcChmod.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibcChmod.java
new file mode 100644
index 0000000..aee2921
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibcChmod.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import com.sun.jna.LastErrorException;
+import org.gradle.internal.nativeplatform.jna.LibC;
+
+import java.io.File;
+import java.io.IOException;
+
+class LibcChmod implements Chmod {
+    private final LibC libc;
+    private final FilePathEncoder encoder;
+
+    public LibcChmod(LibC libc, FilePathEncoder encoder) {
+        this.libc = libc;
+        this.encoder = encoder;
+    }
+
+    public void chmod(File f, int mode) throws IOException {
+        try {
+            byte[] encodedFilePath = encoder.encode(f);
+            libc.chmod(encodedFilePath, mode);
+        } catch (LastErrorException exception) {
+            throw new IOException(String.format("Failed to set file permissions %s on file %s. errno: %d", mode, f.getName(), exception.getErrorCode()));
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibcSymlink.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibcSymlink.java
new file mode 100644
index 0000000..a54432f
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/LibcSymlink.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import com.sun.jna.LastErrorException;
+import org.gradle.internal.nativeplatform.jna.LibC;
+
+import java.io.File;
+import java.io.IOException;
+
+public class LibcSymlink implements Symlink {
+    private final LibC libC;
+
+    public LibcSymlink(LibC libC) {
+        this.libC = libC;
+    }
+
+    public void symlink(File link, File target) throws IOException {
+        link.getParentFile().mkdirs();
+        try {
+            libC.symlink(target.getPath(), link.getPath());
+        } catch (LastErrorException e) {
+            throw new IOException(String.format("Could not create symlink from '%s' to '%s'. Errno is %s.", link.getPath(), target.getPath(), e.getErrorCode()));
+        }
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/MacFilePathEncoder.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/MacFilePathEncoder.java
new file mode 100644
index 0000000..0b0ec11
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/MacFilePathEncoder.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+
+class MacFilePathEncoder implements FilePathEncoder {
+    public byte[] encode(File file) {
+        byte[] encoded;
+        try {
+            encoded = file.getAbsolutePath().getBytes("utf-8");
+        } catch (UnsupportedEncodingException e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        byte[] zeroTerminatedByteArray = new byte[encoded.length + 1];
+        System.arraycopy(encoded, 0, zeroTerminatedByteArray, 0, encoded.length);
+        zeroTerminatedByteArray[encoded.length] = 0;
+        return zeroTerminatedByteArray;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixStat.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixStat.java
new file mode 100644
index 0000000..a43221c
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixStat.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import org.jruby.ext.posix.POSIX;
+
+import java.io.File;
+import java.io.IOException;
+
+class PosixStat implements Stat {
+    private final POSIX posix;
+
+    public PosixStat(POSIX posix) {
+        this.posix = posix;
+    }
+
+    public int getUnixMode(File f) throws IOException {
+        return this.posix.stat(f.getAbsolutePath()).mode() & 0777;
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixUtil.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixUtil.java
index 907ea1c..7d236b3 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixUtil.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/PosixUtil.java
@@ -16,29 +16,21 @@
 
 package org.gradle.internal.nativeplatform.filesystem;
 
-import org.jruby.ext.posix.*;
+import org.jruby.ext.posix.POSIX;
+import org.jruby.ext.posix.POSIXFactory;
+import org.jruby.ext.posix.POSIXHandler;
 
 import java.io.File;
 import java.io.InputStream;
 import java.io.PrintStream;
 
 public class PosixUtil {
-    private static final POSIX POSIX = FallbackAwarePosixFactory.getPOSIX();
+    private static final POSIX POSIX = POSIXFactory.getPOSIX(new POSIXHandlerImpl(), true);
 
     public static POSIX current() {
         return POSIX;
     }
 
-    private static class FallbackAwarePosixFactory{
-        public static POSIX getPOSIX() {
-            POSIX posix = POSIXFactory.getPOSIX(new POSIXHandlerImpl(), true);
-            if(posix instanceof JavaPOSIX || posix instanceof WindowsPOSIX){
-                return new FallbackPOSIX();
-            }
-            return posix;
-        }
-    }
-
     private static class POSIXHandlerImpl implements POSIXHandler {
         public void error(POSIX.ERRORS error, String message) {
             throw new UnsupportedOperationException(error + " - " + message);
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Stat.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Stat.java
new file mode 100644
index 0000000..f201a2b
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Stat.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface Stat {
+    public int getUnixMode(File f) throws IOException;
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Symlink.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Symlink.java
new file mode 100644
index 0000000..f2925b2
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/Symlink.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface Symlink {
+    void symlink(File link, File target) throws IOException;
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandler.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandler.java
index 454a045..c96de84 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandler.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandler.java
@@ -22,14 +22,14 @@ import java.nio.file.Files;
 import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFileAttributes;
 
-
 import org.gradle.internal.nativeplatform.NativeIntegrationException;
-import org.gradle.internal.nativeplatform.filesystem.FilePermissionHandler;
+import org.gradle.internal.nativeplatform.filesystem.Chmod;
+import org.gradle.internal.nativeplatform.filesystem.Stat;
 
 import static org.gradle.internal.nativeplatform.filesystem.jdk7.PosixFilePermissionConverter.convertToInt;
 import static org.gradle.internal.nativeplatform.filesystem.jdk7.PosixFilePermissionConverter.convertToPermissionsSet;
 
-public class PosixJdk7FilePermissionHandler implements FilePermissionHandler {
+public class PosixJdk7FilePermissionHandler implements Stat, Chmod {
 
     public int getUnixMode(File file) {
         try {
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java
index 68ff7da..71fdc80 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java
@@ -17,6 +17,8 @@ package org.gradle.internal.nativeplatform.jna;
 
 import com.sun.jna.LastErrorException;
 import com.sun.jna.Library;
+import com.sun.jna.WString;
+import org.jruby.ext.posix.FileStat;
 
 public interface LibC extends Library {
     //CHECKSTYLE:OFF
@@ -26,6 +28,10 @@ public interface LibC extends Library {
     public int chdir(String dirAbsolutePath) throws LastErrorException;
     public int getpid();
     public int isatty(int fdes);
-    public int chmod(String filename, int mode) throws LastErrorException;
+    public int stat(byte[] filePath, FileStat fileStat) throws LastErrorException;
+    public int __xstat64(int version, byte[] filePath, FileStat fileStat) throws LastErrorException;
+    public int chmod(byte[] filePath, int mode) throws LastErrorException;
+    public int wcstombs(byte[] dest, WString source, int size) throws LastErrorException;
+    public int symlink(String target, String link) throws LastErrorException;
     //CHECKSTYLE:ON
 }
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
index d6d6fac..70a57ed 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/CommonFileSystemTest.groovy
@@ -25,7 +25,7 @@ class CommonFileSystemTest extends Specification {
     @Rule TemporaryFolder tmpDir
 
     def fs = FileSystems.default
-    
+
     def "unix permissions cannot be read on non existing file"() {
         when:
         fs.getUnixMode(tmpDir.file("someFile"))
@@ -44,7 +44,7 @@ class CommonFileSystemTest extends Specification {
 
     @Requires(TestPrecondition.FILE_PERMISSIONS)
     def "unix permissions on files can be changed and read"() {
-        def f = tmpDir.createFile("someFile")
+        def f = tmpDir.createFile("someFile\u03B1.txt")
 
         when:
         fs.chmod(f, mode)
@@ -59,7 +59,7 @@ class CommonFileSystemTest extends Specification {
 
     @Requires(TestPrecondition.FILE_PERMISSIONS)
     def "unix permissions on directories can be changed and read"() {
-        def d = tmpDir.createDir("someDir")
+        def d = tmpDir.createDir("someDir\u03B1")
 
         when:
         fs.chmod(d, mode)
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandlerTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandlerTest.groovy
deleted file mode 100644
index 49c0774..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/ComposableFilePermissionHandlerTest.groovy
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem
-
-import org.jruby.ext.posix.FileStat
-import org.jruby.ext.posix.POSIX
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
-
-class ComposableFilePermissionHandlerTest extends Specification {
-    final ComposableFilePermissionHandler.Chmod chmod = Mock()
-    final POSIX posix = Mock()
-    final ComposableFilePermissionHandler handler = new ComposableFilePermissionHandler(chmod, posix)
-
-    @Rule TemporaryFolder temporaryFolder;
-    def "chmod calls are delegated to Chmod"(){
-        setup:
-        def file = temporaryFolder.createFile("testfile");
-        when:
-        handler.chmod(file, 0744);
-
-        then:
-        1 * chmod.chmod(file, 0744)
-    }
-
-    def "getUnixMode calls are delegated to POSIX"(){
-        setup:
-        FileStat stat = Mock()
-        def file = temporaryFolder.createFile("testfile");
-        posix.stat(file.getAbsolutePath()) >> stat
-        stat.mode() >> 0754
-
-        expect:
-        handler.getUnixMode(file) == 0754
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackFileStatTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackFileStatTest.groovy
deleted file mode 100644
index 42e4648..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackFileStatTest.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem
-
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
-
-class FallbackFileStatTest extends Specification {
-
-    @Rule TemporaryFolder temporaryFolder;
-
-    def "mode() returns FileSystem.DEFAULT_FILE_MODE for files"() {
-        setup:
-        def testfile = temporaryFolder.createFile("testFile")
-        FallbackFileStat fallbackFileStat = new FallbackFileStat(testfile.absolutePath)
-        expect:
-        FileSystem.DEFAULT_FILE_MODE == fallbackFileStat.mode()
-    }
-
-    def "mode() returns FileSystem.DEFAULT_DIR_MODE for directories"() {
-        setup:
-        def testfolder = temporaryFolder.createDir()
-        FallbackFileStat fallbackFileStat = new FallbackFileStat(testfolder.absolutePath)
-        expect:
-        FileSystem.DEFAULT_DIR_MODE == fallbackFileStat.mode()
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIXTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIXTest.groovy
deleted file mode 100644
index fe496d8..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FallbackPOSIXTest.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem
-
-import org.junit.Rule
-import spock.lang.Specification
-import org.gradle.util.TemporaryFolder
-import org.jruby.ext.posix.POSIX
-
-class FallbackPOSIXTest extends Specification {
-
-    FallbackPOSIX posix = new FallbackPOSIX()
-
-    @Rule TemporaryFolder tempFolder;
-
-    def "returns 0 on chmod calls"() {
-        setup:
-        def testFile = tempFolder.createFile("testFile");
-        expect:
-        0 == posix.chmod(testFile.absolutePath, mode)
-        where:
-        mode << [644, 755, 777];
-    }
-
-    def "stat() returns instance of FallbackStat"() {
-        setup:
-        def testFile = tempFolder.createDir();
-        when:
-        def stat = posix.stat(testFile.absolutePath)
-        then:
-        stat instanceof FallbackFileStat
-    }
-
-    def "returns errno code 1 (ENOTSUP) for symlink calls"() {
-        expect:
-        1 == posix.symlink("/old/path", "new/path")
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnJdk7Test.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnJdk7Test.groovy
deleted file mode 100644
index 531289e..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnJdk7Test.groovy
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem
-
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
-
- at Requires(TestPrecondition.JDK7)
-class FilePermissionHandlerFactoryOnJdk7Test extends Specification {
-
-    @Rule TemporaryFolder temporaryFolder
-
-    @Requires(TestPrecondition.WINDOWS)
-    def "createChmod creates EmptyChmod instance on Windows OS"() {
-        when:
-        def chmod = FilePermissionHandlerFactory.createChmod()
-        then:
-        chmod instanceof FilePermissionHandlerFactory.EmptyChmod
-    }
-
-    @Requires(TestPrecondition.WINDOWS)
-    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with disabled Chmod on Windows OS"() {
-        def File file = temporaryFolder.createFile("testFile")
-        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
-        when:
-        handler.chmod(file, mode);
-        then:
-        644 == handler.getUnixMode(file);
-        where:
-        mode << [0722, 0644, 0744, 0755]
-    }
-
-    @Requires(TestPrecondition.FILE_PERMISSIONS)
-    def "createDefaultFilePermissionHandler creates Jdk7PosixFilePermissionHandler on JDK7 with Posix Fs"() {
-        when:
-        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
-        then:
-        handler.getClass().name == "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler"
-    }
-
-    @Requires(TestPrecondition.UNKNOWN_OS)
-    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with disabled Chmod on Unknown OS"() {
-        setup:
-        def File file = temporaryFolder.createFile("testFile")
-        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
-        def originalMode = handler.getUnixMode(file);
-        when:
-        handler.chmod(file, mode);
-        then:
-        originalMode == handler.getUnixMode(file);
-        where:
-        mode << [0722, 0644, 0744, 0755]
-    }
-
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnNonJdk7Test.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnNonJdk7Test.groovy
deleted file mode 100644
index a0c45b2..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FilePermissionHandlerFactoryOnNonJdk7Test.groovy
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
-
-
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
-import spock.lang.Specification
-import org.junit.Rule
-import org.gradle.util.TemporaryFolder
-
- at Requires(TestPrecondition.NOT_JDK7)
-public class FilePermissionHandlerFactoryOnNonJdk7Test extends Specification {
-
-    @Rule TemporaryFolder temporaryFolder
-
-    @Requires(TestPrecondition.WINDOWS)
-    def "createChmod creates EmptyChmod instance on Windows OS"() {
-        when:
-        def chmod = FilePermissionHandlerFactory.createChmod()
-        then:
-        chmod instanceof FilePermissionHandlerFactory.EmptyChmod
-    }
-
-    @Requires(TestPrecondition.WINDOWS)
-    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with disabled Chmod on Windows OS"() {
-        when:
-        def chmod = FilePermissionHandlerFactory.createChmod()
-        then:
-        chmod instanceof FilePermissionHandlerFactory.EmptyChmod
-    }
-
-    @Requires(TestPrecondition.NOT_WINDOWS)
-    def "createDefaultFilePermissionHandler creates ComposeableFilePermissionHandler if not on Windows OS"() {
-        when:
-        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
-        then:
-        handler instanceof ComposableFilePermissionHandler
-    }
-
-    @Requires(TestPrecondition.FILE_PERMISSIONS)
-    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with enabled Chmod on Unknown OS"() {
-        setup:
-        def file = temporaryFolder.createFile("testFile")
-        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
-        when:
-        handler.chmod(file, mode);
-        then:
-        mode == handler.getUnixMode(file);
-        where:
-        mode << [0722, 0644, 0744, 0755]
-    }
-
-    @Requires(TestPrecondition.FILE_PERMISSIONS)
-    def "Throws IOException for failed chmod calls"() {
-        setup:
-        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
-        def notExistingFile = new File(temporaryFolder.createDir(), "nonexisting.file");
-        when:
-        handler.chmod(notExistingFile, 622);
-        then:
-        def e = thrown(IOException)
-        e.message == "Failed to set file permissions 622 on file nonexisting.file. errno: 2"
-    }
-
-    @Requires(TestPrecondition.UNKNOWN_OS)
-    def "createDefaultFilePermissionHandler creates ComposedFilePermissionHandler with disabled Chmod on Unknown OS"() {
-        setup:
-        def file = temporaryFolder.createFile("testFile")
-
-        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
-        def originalMode = handler.getUnixMode(file);
-        when:
-        handler.chmod(file, mode);
-        then:
-        originalMode == handler.getUnixMode(file);
-        where:
-        mode << [0722, 0644, 0744, 0755]
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy
new file mode 100644
index 0000000..103abe6
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnLinuxTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import org.gradle.util.Requires
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Specification
+
+ at Requires(TestPrecondition.LINUX)
+public class FileSystemServicesOnLinuxTest extends Specification {
+    @Rule TemporaryFolder temporaryFolder
+    final Chmod chmod = FileSystemServices.services.get(Chmod)
+    final Stat stat = FileSystemServices.services.get(Stat)
+    final Symlink symlink = FileSystemServices.services.get(Symlink)
+
+    def "creates LibCChmod on Linux"() {
+        expect:
+        chmod instanceof LibcChmod
+    }
+
+    def "creates LibCStat on Linux"() {
+        expect:
+        stat instanceof LibCStat
+    }
+
+    def "creates LibcSymlink on Linux"() {
+        expect:
+        symlink instanceof LibcSymlink
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy
new file mode 100644
index 0000000..97d9234
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnMacTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import org.gradle.util.Requires
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Specification
+
+ at Requires(TestPrecondition.MAC_OS_X)
+public class FileSystemServicesOnMacTest extends Specification {
+    @Rule TemporaryFolder temporaryFolder
+    final Chmod chmod = FileSystemServices.services.get(Chmod)
+    final Stat stat = FileSystemServices.services.get(Stat)
+    final Symlink symlink = FileSystemServices.services.get(Symlink)
+
+    def "creates LibCChmod on Mac"() {
+        expect:
+        chmod instanceof LibcChmod
+    }
+
+    def "creates LibCStat on Mac"() {
+        expect:
+        stat instanceof LibCStat
+    }
+
+    def "creates LibcSymlink on Mac"() {
+        expect:
+        symlink instanceof LibcSymlink
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy
new file mode 100644
index 0000000..71763b4
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnUnknownOsTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+
+ at Requires(TestPrecondition.UNKNOWN_OS)
+public class FileSystemServicesOnUnknownOsTest extends Specification {
+    @Rule TemporaryFolder temporaryFolder
+    final Chmod chmod = FileSystemServices.services.get(Chmod)
+    final Stat stat = FileSystemServices.services.get(Stat)
+    final Symlink symlink = FileSystemServices.services.get(Symlink)
+
+    @Requires(TestPrecondition.NOT_JDK7)
+    def "creates EmptyChmod when not on JDK7"() {
+        expect:
+        chmod instanceof EmptyChmod
+    }
+
+    @Requires(TestPrecondition.NOT_JDK7)
+    def "creates FallbackStat when not on JDK7"() {
+        expect:
+        stat instanceof FallbackStat
+    }
+
+    @Requires(TestPrecondition.NOT_JDK7)
+    def "creates FallbackSymlink when not on JDK7"() {
+        expect:
+        symlink instanceof FallbackSymlink
+    }
+
+    @Requires(TestPrecondition.JDK7)
+    def "creates Jdk7PosixFilePermissionHandler on JDK7"() {
+        expect:
+        chmod.class.name == "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler"
+        stat.class.name == "org.gradle.internal.nativeplatform.filesystem.jdk7.PosixJdk7FilePermissionHandler"
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy
new file mode 100644
index 0000000..1c8577d
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/FileSystemServicesOnWindowsTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import org.gradle.util.Requires
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Specification
+
+ at Requires(TestPrecondition.WINDOWS)
+public class FileSystemServicesOnWindowsTest extends Specification {
+    @Rule TemporaryFolder temporaryFolder
+    final Chmod chmod = FileSystemServices.services.get(Chmod)
+    final Stat stat = FileSystemServices.services.get(Stat)
+    final Symlink symlink = FileSystemServices.services.get(Symlink)
+
+    def "creates EmptyChmod instance on Windows OS"() {
+        expect:
+        chmod instanceof EmptyChmod
+    }
+
+    def "creates FallbackStat instance on Windows OS"() {
+        expect:
+        stat instanceof FallbackStat
+    }
+
+    def "creates FallbackSymlink on Windows OS"() {
+        expect:
+        symlink instanceof FallbackSymlink
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LibcStatTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LibcStatTest.groovy
new file mode 100644
index 0000000..6c1437c
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/LibcStatTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem;
+
+import spock.lang.Specification
+import org.gradle.internal.os.OperatingSystem
+import org.gradle.internal.nativeplatform.jna.LibC
+import org.jruby.ext.posix.BaseNativePOSIX
+import spock.lang.Unroll
+import org.jruby.ext.posix.FileStat;
+
+class LibcStatTest extends Specification {
+
+    OperatingSystem os = Mock();
+    LibC libc = Mock()
+    BaseNativePOSIX posix = Mock()
+    FilePathEncoder encoder = Mock()
+
+    @Unroll
+    def "LibCStat on #osname maps to libc #libcMethodName"(){
+        given:
+        1 * os.isMacOsX() >> (osname == "macosx");
+        1 * posix.allocateStat() >> Mock(FileStat)
+        File testFile = new File("a/file/path")
+        LibCStat libCStat = new LibCStat(libc, os, posix, encoder);
+        
+        when:
+        libCStat.getUnixMode(testFile)
+
+        then:
+        1 * libc."${libcMethodName}"(*_);
+        where:
+        osname   | libcMethodName
+        "macosx" | "stat"
+        "linux"  | "__xstat64"
+    }
+}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/PosixUtilTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/PosixUtilTest.groovy
deleted file mode 100644
index 496f6da..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/PosixUtilTest.groovy
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2012 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 org.gradle.internal.nativeplatform.filesystem
-
-import spock.lang.Specification
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition;
-
-public class PosixUtilTest extends Specification {
-
-    @Requires(TestPrecondition.UNKNOWN_OS)
-    def "PosixUtil.current returns FallbackPOSIX on Unknown OS"() {
-        expect:
-        PosixUtil.current() instanceof FallbackPOSIX
-    }
-
-    @Requires(TestPrecondition.WINDOWS)
-    def "PosixUtil.current returns FallbackPOSIX on WindowsOS"() {
-        expect:
-        PosixUtil.current() instanceof FallbackPOSIX
-    }
-
-    @Requires(TestPrecondition.FILE_PERMISSIONS)
-    def "PosixUtil.current returns no FallbackPOSIX on Macosx and Unix"() {
-        expect:
-        !(PosixUtil.current() instanceof FallbackPOSIX)
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy
index e401c89..eccc92b 100644
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/filesystem/jdk7/PosixJdk7FilePermissionHandlerTest.groovy
@@ -19,20 +19,17 @@ package org.gradle.internal.nativeplatform.filesystem.jdk7
 import spock.lang.Specification
 import org.gradle.util.Requires
 import org.gradle.util.TestPrecondition
-import org.gradle.internal.nativeplatform.filesystem.FilePermissionHandlerFactory
 import org.junit.Rule
 import org.gradle.util.TemporaryFolder
 
-
+ at Requires(TestPrecondition.NOT_WINDOWS)
 class PosixJdk7FilePermissionHandlerTest extends Specification {
-
     @Rule TemporaryFolder temporaryFolder
 
-    @Requires(TestPrecondition.NOT_WINDOWS)
     def "test chmod on non windows platforms with JDK7"() {
         setup:
         def file = temporaryFolder.createFile("testFile")
-        def handler = FilePermissionHandlerFactory.createDefaultFilePermissionHandler()
+        def handler = new PosixJdk7FilePermissionHandler()
         when:
         handler.chmod(file, mode);
         then:
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
index b0a45b6..b2e3e8a 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.integtests.openapi
 
-import junit.framework.AssertionFailedError
 import org.gradle.integtests.fixtures.GradleDistribution
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.openapi.external.runner.GradleRunnerFactory
@@ -89,7 +88,7 @@ class GradleRunnerTest {
     }
 
     if( totalWaitTime > maximumWaitTime ) {
-      throw new AssertionFailedError( "Waited $totalWaitTime seconds and failed to finish executing command. This is taking too long, so assuming something is wrong.\n. Interaction: $interaction" )
+      throw new AssertionError( "Waited $totalWaitTime seconds and failed to finish executing command. This is taking too long, so assuming something is wrong.\n. Interaction: $interaction" )
     }
 
     //now make sure we were notified of things correctly:
@@ -150,7 +149,7 @@ class GradleRunnerTest {
     }
 
     if( totalWaitTime > maximumWaitTime ) {
-      throw new AssertionFailedError( "Waited $totalWaitTime seconds and failed to finish executing command. This is taking too long, so assuming something is wrong.\nInteraction: $interaction")
+      throw new AssertionError( "Waited $totalWaitTime seconds and failed to finish executing command. This is taking too long, so assuming something is wrong.\nInteraction: $interaction")
     }
 
     //make sure we tried to kill the task
diff --git a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java
index a10de5d..a631e82 100644
--- a/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java
+++ b/subprojects/open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java
@@ -89,7 +89,7 @@ public class ExternalUtility {
     private static StringBuilder createFileNamesString(File[] files) {
         StringBuilder fileNames = new StringBuilder();
         for (File f : files) {
-            fileNames.append(f.getName() + ", ");
+            fileNames.append(f.getAbsolutePath() + ", ");
         }
         fileNames.delete(fileNames.length() - 2, fileNames.length()); // Remove the trailing ', '
         return fileNames;
diff --git a/subprojects/osgi/src/integTest/groovy/org/gradle/api/plugins/osgi/OsgiPluginIntegrationSpec.groovy b/subprojects/osgi/src/integTest/groovy/org/gradle/api/plugins/osgi/OsgiPluginIntegrationSpec.groovy
new file mode 100644
index 0000000..672fde0
--- /dev/null
+++ b/subprojects/osgi/src/integTest/groovy/org/gradle/api/plugins/osgi/OsgiPluginIntegrationSpec.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012 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 org.gradle.api.plugins.osgi
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+
+class OsgiPluginIntegrationSpec extends AbstractIntegrationSpec {
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2237")
+    def "can set modelled manifest properties with instruction"() {
+        given:
+        buildFile << """
+            version = "1.0"
+            apply plugin: "java"
+            apply plugin: "osgi"
+                            
+            jar {
+                manifest {
+                    version = "3.0"
+                    instructionReplace("Bundle-Version", "2.0")
+                }
+            }
+        """
+        
+        and:
+        file("src/main/java/Thing.java") << "public class Thing {}"
+        
+        when:
+        run "jar"
+
+        then:
+        file("build/tmp/jar/MANIFEST.MF").text.contains("Bundle-Version: 2.0")
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2237")
+    def "jar task remains incremental"() {
+        given:
+        // Unsure why, but this problem doesn't show if we don't wait a little bit
+        // before the next execution.
+        //
+        // The value that's used is comes from aQute.lib.osgi.Analyzer#calcManifest()
+        // and is set to the current time. I don't have an explanation for why the sleep is needed.
+        // It needs to be about 1000 on my machine.
+        def sleepTime = 1000
+
+        buildFile << """
+            apply plugin: "java"
+            apply plugin: "osgi"
+
+            jar {
+                manifest {
+                    instruction "Bnd-LastModified", "123"
+                }
+            }
+        """
+
+        and:
+        file("src/main/java/Thing.java") << "public class Thing {}"
+
+        when:
+        run "jar"
+        
+        then:
+        ":jar" in nonSkippedTasks
+        
+        when:
+        sleep sleepTime
+        run "jar"
+
+        then:
+        ":jar" in skippedTasks
+
+        when:
+        sleep sleepTime
+        run "clean", "jar"
+
+        then:
+        ":jar" in nonSkippedTasks
+    }
+
+    private waitForMinimumBndLastModifiedInterval() {
+
+        sleep 1000
+    }
+
+}
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
index fb4136e..2aa4750 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
@@ -17,11 +17,11 @@ package org.gradle.api.internal.plugins.osgi;
 
 import aQute.lib.osgi.Analyzer;
 import org.gradle.api.file.FileCollection;
-import org.gradle.internal.Factory;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.java.archives.Attributes;
 import org.gradle.api.java.archives.internal.DefaultManifest;
 import org.gradle.api.plugins.osgi.OsgiManifest;
+import org.gradle.internal.Factory;
 import org.gradle.internal.UncheckedException;
 import org.gradle.util.GUtil;
 import org.gradle.util.WrapUtil;
@@ -35,14 +35,6 @@ import java.util.jar.Manifest;
  * @author Hans Dockter
  */
 public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest {
-    private String symbolicName;
-    private String name;
-    private String version;
-    private String description;
-    private String license;
-    private String vendor;
-    private String docURL;
-
     private File classesDir;
 
     private Factory<ContainedVersionAnalyzer> analyzerFactory = new DefaultAnalyzerFactory();
@@ -62,13 +54,20 @@ public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest
         try {
             setAnalyzerProperties(analyzer);
             Manifest osgiManifest = analyzer.calcManifest();
-            for (Map.Entry<Object, Object> entry : osgiManifest.getMainAttributes().entrySet()) {
+            java.util.jar.Attributes attributes = osgiManifest.getMainAttributes();
+            for (Map.Entry<Object, Object> entry : attributes.entrySet()) {
                 effectiveManifest.attributes(WrapUtil.toMap(entry.getKey().toString(), (String) entry.getValue()));
             }
             effectiveManifest.attributes(this.getAttributes());
             for (Map.Entry<String, Attributes> ent : getSections().entrySet()) {
                 effectiveManifest.attributes(ent.getValue(), ent.getKey());
             }
+            if (classesDir != null) {
+                long mod = classesDir.lastModified();
+                if (mod > 0) {
+                    effectiveManifest.getAttributes().put(Analyzer.BND_LASTMODIFIED, mod);
+                }
+            }
         } catch (Exception e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
@@ -87,30 +86,37 @@ public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest
             analyzer.setProperty(Analyzer.IMPORT_PACKAGE,
                     "*, !org.apache.ant.*, !org.junit.*, !org.jmock.*, !org.easymock.*, !org.mockito.*");
         }
+        if(!instructionNames.contains(Analyzer.BUNDLE_VERSION)){
+            analyzer.setProperty(Analyzer.BUNDLE_VERSION, getVersion());
+        }
+        if(!instructionNames.contains(Analyzer.BUNDLE_NAME)){
+            analyzer.setProperty(Analyzer.BUNDLE_NAME, getName());
+        }
+        if(!instructionNames.contains(Analyzer.BUNDLE_SYMBOLICNAME)){
+            analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, getSymbolicName());
+        }
         if (!instructionNames.contains(Analyzer.EXPORT_PACKAGE)) {
-            analyzer.setProperty(Analyzer.EXPORT_PACKAGE, "*;-noimport:=false;version=" + version);
+            analyzer.setProperty(Analyzer.EXPORT_PACKAGE, "*;-noimport:=false;version=" + getVersion());
         }
         for (String instructionName : instructionNames) {
             String list = createPropertyStringFromList(instructionValue(instructionName));
-            analyzer.setProperty(instructionName, list);
+            if (list != null && list.length() > 0) {
+                analyzer.setProperty(instructionName, list);
+            }
         }
 
-        setProperty(analyzer, Analyzer.BUNDLE_VERSION, getVersion());
-        setProperty(analyzer, Analyzer.BUNDLE_SYMBOLICNAME, getSymbolicName());
-        setProperty(analyzer, Analyzer.BUNDLE_NAME, getName());
-        setProperty(analyzer, Analyzer.BUNDLE_DESCRIPTION, getDescription());
-        setProperty(analyzer, Analyzer.BUNDLE_LICENSE, getLicense());
-        setProperty(analyzer, Analyzer.BUNDLE_VENDOR, getVendor());
-        setProperty(analyzer, Analyzer.BUNDLE_DOCURL, getDocURL());
         analyzer.setJar(getClassesDir());
+
         analyzer.setClasspath(getClasspath().getFiles().toArray(new File[getClasspath().getFiles().size()]));
     }
 
-    private void setProperty(Analyzer analyzer, String key, String value) {
-        if (value == null) {
-            return;
+    private String instructionValueString(String instructionName) {
+        List<String> values = instructionValue(instructionName);
+        if (values == null || values.isEmpty()) {
+            return null;
+        } else {
+            return createPropertyStringFromList(values);
         }
-        analyzer.setProperty(key, value);
     }
 
     public List<String> instructionValue(String instructionName) {
@@ -133,72 +139,83 @@ public class DefaultOsgiManifest extends DefaultManifest implements OsgiManifest
         return this;
     }
 
-    public Map<String, List<String>> getInstructions() {
-        return instructions;
+    public OsgiManifest instructionReplace(String name, String... values) {
+        if (values.length == 0 || (values.length == 1 && values[0] == null)) {
+            instructions.remove(name);
+        } else {
+            if (instructions.get(name) == null) {
+                instructions.put(name, new ArrayList<String>());
+            }
+            List<String> instructionsForName = instructions.get(name);
+            instructionsForName.clear();
+            Collections.addAll(instructionsForName, values);
+        }
+
+        return this;
     }
 
-    public void setInstructions(Map<String, List<String>> instructions) {
-        this.instructions = instructions;
+    public Map<String, List<String>> getInstructions() {
+        return instructions;
     }
 
     private String createPropertyStringFromList(List<String> valueList) {
-        return GUtil.join(valueList, ",");
+        return valueList == null || valueList.isEmpty() ? null : GUtil.join(valueList, ",");
     }
 
     public String getSymbolicName() {
-        return symbolicName;
+        return instructionValueString(Analyzer.BUNDLE_SYMBOLICNAME);
     }
 
     public void setSymbolicName(String symbolicName) {
-        this.symbolicName = symbolicName;
+        instructionReplace(Analyzer.BUNDLE_SYMBOLICNAME, symbolicName);
     }
 
     public String getName() {
-        return name;
+        return instructionValueString(Analyzer.BUNDLE_NAME);
     }
 
     public void setName(String name) {
-        this.name = name;
+        instructionReplace(Analyzer.BUNDLE_NAME, name);
     }
 
     public String getVersion() {
-        return version;
+        return instructionValueString(Analyzer.BUNDLE_VERSION);
     }
 
     public void setVersion(String version) {
-        this.version = version;
+        instructionReplace(Analyzer.BUNDLE_VERSION, version);
     }
 
     public String getDescription() {
-        return description;
+        return instructionValueString(Analyzer.BUNDLE_DESCRIPTION);
     }
 
     public void setDescription(String description) {
-        this.description = description;
+        instructionReplace(Analyzer.BUNDLE_DESCRIPTION, description);
     }
 
     public String getLicense() {
-        return license;
+        return instructionValueString(Analyzer.BUNDLE_LICENSE);
     }
 
     public void setLicense(String license) {
-        this.license = license;
+        instructionReplace(Analyzer.BUNDLE_LICENSE, license);
     }
 
     public String getVendor() {
-        return vendor;
+        return instructionValueString(Analyzer.BUNDLE_VENDOR);
     }
 
     public void setVendor(String vendor) {
-        this.vendor = vendor;
+        instructionReplace(Analyzer.BUNDLE_VENDOR, vendor);
     }
 
     public String getDocURL() {
-        return docURL;
+        return instructionValueString(Analyzer.BUNDLE_DOCURL);
     }
 
     public void setDocURL(String docURL) {
-        this.docURL = docURL;
+        instructionReplace(Analyzer.BUNDLE_DOCURL, docURL);
     }
 
     public File getClassesDir() {
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java
index f82b53f..9830064 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java
@@ -61,8 +61,7 @@ public class OsgiHelper {
      * @return Returns the SymbolicName that should be used for the bundle.
      */
     public String getBundleSymbolicName(Project project) {
-
-        String group = (String) project.property("group");
+        String group = project.getGroup().toString();
         String archiveBaseName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName();
         if (archiveBaseName.startsWith(group)) {
             return archiveBaseName;
@@ -141,7 +140,7 @@ public class OsgiHelper {
     }
 
     private String fillQualifier(StringTokenizer st) {
-        StringBuffer buf = new StringBuffer();
+        StringBuilder buf = new StringBuilder();
         while (st.hasMoreTokens()) {
             String token = st.nextToken();
             if (QUALIFIER.matcher(token).matches()) {
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java
index 4ecdf8f..d593706 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java
@@ -44,6 +44,7 @@ public interface OsgiManifest extends org.gradle.api.java.archives.Manifest {
      * @param values
      * @return this
      * @see #instructionFirst(String, String...)
+     * @see #instructionReplace(String, String...)
      */
     OsgiManifest instruction(String name, String... values);
 
@@ -54,11 +55,24 @@ public interface OsgiManifest extends org.gradle.api.java.archives.Manifest {
      * @param name Name of the instruction.
      * @param values The values for the instruction.
      * @return this
-     * @see #instructionFirst(String, String...)
+     * @see #instruction(String, String...)
+     * @see #instructionReplace(String, String...)
      */
     OsgiManifest instructionFirst(String name, String... values);
 
     /**
+     * Sets the values for an instruction. If the instruction does not exists, it is created. If it does exists, the
+     * values replace the existing values.
+     *
+     * @param name Name of the instruction.
+     * @param values The values for the instruction.
+     * @return this
+     * @see #instruction(String, String...)
+     * @see #instructionFirst(String, String...)
+     */
+    OsgiManifest instructionReplace(String name, String... values);
+
+    /**
      * Returns all exisiting instruction.
      *
      * @return A map with instructions. The key of the map is the instruction name, the value a list of arguments.
diff --git a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
index 023d8a9..d69aa58 100644
--- a/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
+++ b/subprojects/osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
@@ -16,12 +16,17 @@
 package org.gradle.api.plugins.osgi;
 
 import groovy.lang.Closure;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.IConventionAware;
 import org.gradle.api.internal.plugins.osgi.DefaultOsgiManifest;
 import org.gradle.api.internal.plugins.osgi.OsgiHelper;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.plugins.BasePluginConvention;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 
+import java.util.concurrent.Callable;
+
 /**
  * Is mixed in into the project when applying the  {@link org.gradle.api.plugins.osgi.OsgiPlugin} .
  *
@@ -65,12 +70,27 @@ public class OsgiPluginConvention {
         return ConfigureUtil.configure(closure, createDefaultOsgiManifest(project));
     }
 
-    private OsgiManifest createDefaultOsgiManifest(ProjectInternal project) {
-        OsgiHelper osgiHelper = new OsgiHelper();
-        OsgiManifest osgiManifest = new DefaultOsgiManifest(project.getFileResolver());
-        osgiManifest.setVersion(osgiHelper.getVersion((String) project.property("version")));
-        osgiManifest.setName(project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName());
-        osgiManifest.setSymbolicName(osgiHelper.getBundleSymbolicName(project));
+    private OsgiManifest createDefaultOsgiManifest(final ProjectInternal project) {
+        OsgiManifest osgiManifest = project.getServices().get(Instantiator.class).newInstance(DefaultOsgiManifest.class, project.getFileResolver());
+        ConventionMapping mapping = ((IConventionAware) osgiManifest).getConventionMapping();
+        final OsgiHelper osgiHelper = new OsgiHelper();
+
+        mapping.map("version", new Callable<Object>() {
+            public Object call() throws Exception {
+                return osgiHelper.getVersion(project.getVersion().toString());
+            }
+        });
+        mapping.map("name", new Callable<Object>() {
+            public Object call() throws Exception {
+                return project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName();
+            }
+        });
+        mapping.map("symbolicName", new Callable<Object>() {
+            public Object call() throws Exception {
+                return osgiHelper.getBundleSymbolicName(project);
+            }
+        });
+
         return osgiManifest;
     }
 }
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.groovy b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.groovy
new file mode 100644
index 0000000..a394af1
--- /dev/null
+++ b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.groovy
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2011 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 org.gradle.api.internal.plugins.osgi
+
+import aQute.lib.osgi.Analyzer
+import java.util.jar.Manifest
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.java.archives.Attributes
+import org.gradle.api.java.archives.internal.DefaultAttributes
+import org.gradle.api.java.archives.internal.DefaultManifest
+import org.gradle.internal.Factory
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultOsgiManifestTest extends Specification {
+    private static final String ARBITRARY_SECTION = "A-Different-Section"
+    private static final String ARBITRARY_ATTRIBUTE = "Silly-Attribute"
+    private static final String ANOTHER_ARBITRARY_ATTRIBUTE = "Serious-Attribute"
+
+    DefaultOsgiManifest osgiManifest
+
+    Factory<ContainedVersionAnalyzer> analyzerFactoryMock = Mock(Factory)
+    ContainedVersionAnalyzer analyzerMock = Mock(ContainedVersionAnalyzer)
+
+    FileResolver fileResolver = Mock(FileResolver)
+
+    @Shared specialFields = [
+            ["description", Analyzer.BUNDLE_DESCRIPTION],
+            ["docURL", Analyzer.BUNDLE_DOCURL],
+            ["license", Analyzer.BUNDLE_LICENSE],
+            ["name", Analyzer.BUNDLE_NAME],
+            ["symbolicName", Analyzer.BUNDLE_SYMBOLICNAME],
+            ["vendor", Analyzer.BUNDLE_VENDOR],
+            ["version", Analyzer.BUNDLE_VERSION]
+    ]
+
+    def setup() {
+        osgiManifest = new DefaultOsgiManifest(fileResolver)
+        interaction {
+            _ * analyzerFactoryMock.create() >> analyzerMock
+        }
+        osgiManifest.analyzerFactory = analyzerFactoryMock
+    }
+
+    def initialState() {
+        expect:
+        osgiManifest.instructions.isEmpty()
+        osgiManifest.analyzerFactory != null
+    }
+
+    @Unroll
+    "set then get - #field"() {
+        given:
+        def testValue = "testValue"
+
+        when:
+        osgiManifest."$field" = testValue
+
+        then:
+        osgiManifest."$field" == testValue
+
+        where:
+        field << specialFields.collect { it[0] }
+    }
+
+    @Unroll
+    "can mix and match properties and instructions - #field"(String field, String name) {
+        given:
+        def testValue = "testValue"
+
+        when:
+        osgiManifest.instruction(name, testValue)
+
+        then:
+        osgiManifest."$field" == testValue
+        osgiManifest.instructionValue(name) == [testValue]
+
+        when:
+        testValue = "changed"
+
+        and:
+        osgiManifest."$field" = testValue
+
+        then:
+        osgiManifest."$field" == testValue
+        osgiManifest.instructionValue(name) == [testValue]
+
+        when:
+        osgiManifest.instruction(name, "other")
+
+        then:
+        osgiManifest."$field" == "$testValue,other"
+
+        where:
+        [field, name] << specialFields
+    }
+
+    @Unroll
+    "can set modelled properties with instruction - #name"(String field, String name) {
+        given:
+        setUpOsgiManifest()
+        def testValue = "testValue"
+
+        when:
+        osgiManifest.instructionReplace(name, testValue)
+
+        and:
+        prepareMock()
+        
+        then:
+        def effectiveManifest = osgiManifest.getEffectiveManifest()
+        effectiveManifest.attributes[name] == testValue
+
+        where:
+        [field, name] << specialFields
+    }
+
+    private DefaultOsgiManifest createManifest() {
+        return new DefaultOsgiManifest(fileResolver)
+    }
+
+    def addInstruction() {
+        given:
+        String testInstructionName = "someInstruction"
+        String instructionValue1 = "value1"
+        String instructionValue2 = "value2"
+        String instructionValue3 = "value3"
+
+        expect:
+        osgiManifest.is osgiManifest.instruction(testInstructionName, instructionValue1, instructionValue2)
+        osgiManifest.instructions[testInstructionName] == [instructionValue1, instructionValue2]
+
+        when:
+        osgiManifest.instruction(testInstructionName, instructionValue3)
+
+        then:
+        osgiManifest.instructions[testInstructionName] == [instructionValue1, instructionValue2, instructionValue3]
+    }
+
+    def addInstructionFirst() {
+        given:
+        String testInstructionName = "someInstruction"
+        String instructionValue1 = "value1"
+        String instructionValue2 = "value2"
+        String instructionValue3 = "value3"
+
+        expect:
+        osgiManifest.is osgiManifest.instructionFirst(testInstructionName, instructionValue1, instructionValue2)
+        osgiManifest.instructions[testInstructionName] == [instructionValue1, instructionValue2]
+
+        when:
+        osgiManifest.instructionFirst(testInstructionName, instructionValue3)
+
+        then:
+        osgiManifest.instructions[testInstructionName] == [instructionValue3, instructionValue1, instructionValue2]
+    }
+
+    def instructionValue() {
+        given:
+        String testInstructionName = "someInstruction"
+        String instructionValue1 = "value1"
+        String instructionValue2 = "value2"
+
+        when:
+        osgiManifest.instruction(testInstructionName, instructionValue1, instructionValue2)
+
+        then:
+        osgiManifest.instructionValue(testInstructionName) == [instructionValue1, instructionValue2]
+    }
+
+    def getEffectiveManifest() {
+        given:
+        setUpOsgiManifest()
+        prepareMock()
+
+        when:
+        DefaultManifest manifest = osgiManifest.getEffectiveManifest()
+        DefaultManifest defaultManifest = getDefaultManifestWithOsgiValues()
+        DefaultManifest expectedManifest = new DefaultManifest(fileResolver).attributes(defaultManifest.getAttributes())
+        for (Map.Entry<String, Attributes> ent: defaultManifest.getSections().entrySet()) {
+            expectedManifest.attributes(ent.getValue(), ent.getKey())
+        }
+
+        then:
+        manifest.attributes == expectedManifest.attributes
+        manifest.sections == expectedManifest.sections
+    }
+
+    def merge() {
+        given:
+        setUpOsgiManifest()
+        prepareMock()
+
+        when:
+        DefaultManifest otherManifest = new DefaultManifest(fileResolver)
+        otherManifest.mainAttributes(somekey: "somevalue")
+        otherManifest.mainAttributes((Analyzer.BUNDLE_VENDOR): "mergeVendor")
+        osgiManifest.from(otherManifest)
+        DefaultManifest defaultManifest = getDefaultManifestWithOsgiValues()
+        DefaultManifest expectedManifest = new DefaultManifest(fileResolver).attributes(defaultManifest.getAttributes())
+        for (Map.Entry<String, Attributes> ent: defaultManifest.getSections().entrySet()) {
+            expectedManifest.attributes(ent.getValue(), ent.getKey())
+        }
+        expectedManifest.attributes(otherManifest.getAttributes())
+
+        DefaultManifest manifest = osgiManifest.getEffectiveManifest()
+
+        then:
+        manifest.isEqualsTo expectedManifest
+    }
+
+    def generateWithNull() {
+        given:
+        setUpOsgiManifest()
+        prepareMockForNullTest()
+
+        when:
+        osgiManifest.setVersion(null)
+
+        then:
+        osgiManifest.effectiveManifest
+    }
+
+    private setUpOsgiManifest() {
+        def fileCollection = Mock(FileCollection)
+        interaction {
+            _ * fileCollection.files >> ([new File("someFile")] as Set)
+        }
+
+        osgiManifest.setSymbolicName("symbolic")
+        osgiManifest.setName("myName")
+        osgiManifest.setVersion("myVersion")
+        osgiManifest.setDescription("myDescription")
+        osgiManifest.setLicense("myLicense")
+        osgiManifest.setVendor("myVendor")
+        osgiManifest.setDocURL("myDocUrl")
+        osgiManifest.instruction(Analyzer.EXPORT_PACKAGE, "pack1", "pack2")
+        osgiManifest.instruction(Analyzer.IMPORT_PACKAGE, "pack3", "pack4")
+        osgiManifest.setClasspath(fileCollection)
+        osgiManifest.setClassesDir(new File("someDir"))
+        addPlainAttributesAndSections(osgiManifest)
+    }
+
+    private prepareMock() {
+        interaction {
+            1 * analyzerMock.setProperty(Analyzer.BUNDLE_VERSION, osgiManifest.version)
+        }
+        prepareMockForNullTest()
+    }
+
+    private prepareMockForNullTest() {
+        interaction {
+            1 * analyzerMock.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, osgiManifest.symbolicName)
+            1 * analyzerMock.setProperty(Analyzer.BUNDLE_NAME, osgiManifest.name)
+            1 * analyzerMock.setProperty(Analyzer.BUNDLE_DESCRIPTION, osgiManifest.description)
+            1 * analyzerMock.setProperty(Analyzer.BUNDLE_LICENSE, osgiManifest.license)
+            1 * analyzerMock.setProperty(Analyzer.BUNDLE_VENDOR, osgiManifest.vendor)
+            1 * analyzerMock.setProperty(Analyzer.BUNDLE_DOCURL, osgiManifest.docURL)
+            1 * analyzerMock.setProperty(Analyzer.EXPORT_PACKAGE, osgiManifest.instructionValue(Analyzer.EXPORT_PACKAGE).join(","))
+            1 * analyzerMock.setProperty(Analyzer.IMPORT_PACKAGE, osgiManifest.instructionValue(Analyzer.IMPORT_PACKAGE).join(","))
+
+            1 * analyzerMock.setProperty(ARBITRARY_ATTRIBUTE, "I like green eggs and ham.")
+
+            1 * analyzerMock.setJar(osgiManifest.classesDir)
+            1 * analyzerMock.setClasspath(osgiManifest.classpath.files.toArray())
+
+            Manifest testManifest = new Manifest()
+            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_SYMBOLICNAME, osgiManifest.getSymbolicName())
+            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_NAME, osgiManifest.getName())
+            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_DESCRIPTION, osgiManifest.getDescription())
+            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_LICENSE, osgiManifest.getLicense())
+            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_VENDOR, osgiManifest.getVendor())
+            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_DOCURL, osgiManifest.getDocURL())
+            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_VERSION, osgiManifest.getVersion())
+            testManifest.getMainAttributes().putValue(Analyzer.EXPORT_PACKAGE, osgiManifest.instructionValue(Analyzer.EXPORT_PACKAGE).join(","))
+            testManifest.getMainAttributes().putValue(Analyzer.IMPORT_PACKAGE, osgiManifest.instructionValue(Analyzer.IMPORT_PACKAGE).join(","))
+
+            _ * analyzerMock.calcManifest() >> testManifest
+
+        }
+    }
+
+    private DefaultManifest getDefaultManifestWithOsgiValues() {
+        DefaultManifest manifest = new DefaultManifest(fileResolver)
+        manifest.getAttributes().put(Analyzer.BUNDLE_SYMBOLICNAME, osgiManifest.getSymbolicName())
+        manifest.getAttributes().put(Analyzer.BUNDLE_NAME, osgiManifest.getName())
+        manifest.getAttributes().put(Analyzer.BUNDLE_DESCRIPTION, osgiManifest.getDescription())
+        manifest.getAttributes().put(Analyzer.BUNDLE_LICENSE, osgiManifest.getLicense())
+        manifest.getAttributes().put(Analyzer.BUNDLE_VENDOR, osgiManifest.getVendor())
+        manifest.getAttributes().put(Analyzer.BUNDLE_VERSION, osgiManifest.getVersion())
+        manifest.getAttributes().put(Analyzer.BUNDLE_DOCURL, osgiManifest.getDocURL())
+        manifest.getAttributes().put(Analyzer.EXPORT_PACKAGE, osgiManifest.instructionValue(Analyzer.EXPORT_PACKAGE).join(","))
+        manifest.getAttributes().put(Analyzer.IMPORT_PACKAGE, osgiManifest.instructionValue(Analyzer.IMPORT_PACKAGE).join(","))
+        addPlainAttributesAndSections(manifest)
+        return manifest
+    }
+
+    private void addPlainAttributesAndSections(DefaultManifest manifest) {
+        manifest.getAttributes().put(ARBITRARY_ATTRIBUTE, "I like green eggs and ham.")
+        Attributes sectionAtts = new DefaultAttributes()
+        sectionAtts.put(ANOTHER_ARBITRARY_ATTRIBUTE, "Death is the great equalizer.")
+        manifest.getSections().put(ARBITRARY_SECTION, sectionAtts)
+    }
+}
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java b/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
deleted file mode 100644
index 9487c0e..0000000
--- a/subprojects/osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.api.internal.plugins.osgi;
-
-import aQute.lib.osgi.Analyzer;
-import org.gradle.api.file.FileCollection;
-import org.gradle.internal.Factory;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.java.archives.Attributes;
-import org.gradle.api.java.archives.internal.DefaultAttributes;
-import org.gradle.api.java.archives.internal.DefaultManifest;
-import org.gradle.util.GUtil;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.WrapUtil;
-import org.hamcrest.Matchers;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-import java.util.jar.Manifest;
-
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultOsgiManifestTest {
-    private static final String ARBITRARY_SECTION = "A-Different-Section";
-    private static final String ARBITRARY_ATTRIBUTE = "Silly-Attribute";
-    private static final String ANOTHER_ARBITRARY_ATTRIBUTE = "Serious-Attribute";
-
-    private JUnit4Mockery context = new JUnit4GroovyMockery();
-    private DefaultOsgiManifest osgiManifest;
-    @SuppressWarnings("unchecked")
-    private Factory<ContainedVersionAnalyzer> analyzerFactoryMock = context.mock(Factory.class);
-    private ContainedVersionAnalyzer analyzerMock;
-
-    private FileResolver fileResolver = context.mock(FileResolver.class);
-
-    @Before
-    public void setUp() {
-        osgiManifest = new DefaultOsgiManifest(fileResolver);
-        analyzerMock = context.mock(ContainedVersionAnalyzer.class);
-        context.checking(new Expectations() {{
-            allowing(analyzerFactoryMock).create();
-            will(returnValue(analyzerMock));
-        }});
-        osgiManifest.setAnalyzerFactory(analyzerFactoryMock);
-    }
-
-    @Test
-    public void init() {
-        assertEquals(0, osgiManifest.getInstructions().size());
-        assertNotNull(osgiManifest.getAnalyzerFactory());
-    }
-
-    @Test
-    public void setterGetter() {
-        String testValue = "testValue";
-        osgiManifest.setDescription(testValue);
-        assertEquals(testValue, osgiManifest.getDescription());
-        osgiManifest.setDocURL(testValue);
-        assertEquals(testValue, osgiManifest.getDocURL());
-        osgiManifest.setLicense(testValue);
-        assertEquals(testValue, osgiManifest.getLicense());
-        osgiManifest.setName(testValue);
-        assertEquals(testValue, osgiManifest.getName());
-        osgiManifest.setSymbolicName(testValue);
-        assertEquals(testValue, osgiManifest.getSymbolicName());
-        osgiManifest.setVendor(testValue);
-        assertEquals(testValue, osgiManifest.getVendor());
-        osgiManifest.setVersion(testValue);
-        assertEquals(testValue, osgiManifest.getVersion());
-    }
-
-    @Test
-    public void addInstruction() {
-        String testInstructionName = "someInstruction";
-        String instructionValue1 = "value1";
-        String instructionValue2 = "value2";
-        String instructionValue3 = "value3";
-        assertSame(osgiManifest, osgiManifest.instruction(testInstructionName, instructionValue1, instructionValue2));
-        assertEquals(WrapUtil.toList(instructionValue1, instructionValue2), osgiManifest.getInstructions().get(testInstructionName));
-        osgiManifest.instruction(testInstructionName, instructionValue3);
-        assertEquals(WrapUtil.toList(instructionValue1, instructionValue2, instructionValue3),
-                osgiManifest.getInstructions().get(testInstructionName));
-    }
-
-    @Test
-    public void addInstructionFirst() {
-        String testInstructionName = "someInstruction";
-        String instructionValue1 = "value1";
-        String instructionValue2 = "value2";
-        String instructionValue3 = "value3";
-        assertSame(osgiManifest, osgiManifest.instructionFirst(testInstructionName, instructionValue1, instructionValue2));
-        assertEquals(WrapUtil.toList(instructionValue1, instructionValue2), osgiManifest.getInstructions().get(testInstructionName));
-        osgiManifest.instructionFirst(testInstructionName, instructionValue3);
-        assertEquals(WrapUtil.toList(instructionValue3, instructionValue1, instructionValue2),
-                osgiManifest.getInstructions().get(testInstructionName));
-    }
-
-    @Test
-    public void instructionValue() {
-        String testInstructionName = "someInstruction";
-        String instructionValue1 = "value1";
-        String instructionValue2 = "value2";
-        osgiManifest.instruction(testInstructionName, instructionValue1, instructionValue2);
-        assertEquals(WrapUtil.toList(instructionValue1, instructionValue2), osgiManifest.instructionValue(testInstructionName));
-    }
-
-    @Test
-    public void getEffectiveManifest() throws Exception {
-        setUpOsgiManifest();
-        prepareMock();
-
-        DefaultManifest manifest = osgiManifest.getEffectiveManifest();
-        DefaultManifest defaultManifest = getDefaultManifestWithOsgiValues();
-        DefaultManifest expectedManifest = new DefaultManifest(fileResolver).attributes(defaultManifest.getAttributes());
-        for(Map.Entry<String, Attributes> ent : defaultManifest.getSections().entrySet()) {
-            expectedManifest.attributes(ent.getValue(), ent.getKey());
-        }
-
-        assertThat(manifest.getAttributes(), Matchers.equalTo(expectedManifest.getAttributes()));
-        assertThat(manifest.getSections(), Matchers.equalTo(expectedManifest.getSections()));
-    }
-
-    @Test
-    public void merge() throws Exception {
-        setUpOsgiManifest();
-        prepareMock();
-        DefaultManifest otherManifest = new DefaultManifest(fileResolver);
-        otherManifest.mainAttributes(WrapUtil.toMap("somekey", "somevalue"));
-        otherManifest.mainAttributes(WrapUtil.toMap(Analyzer.BUNDLE_VENDOR, "mergeVendor"));
-        osgiManifest.from(otherManifest);
-        DefaultManifest defaultManifest = getDefaultManifestWithOsgiValues();
-        DefaultManifest expectedManifest = new DefaultManifest(fileResolver).attributes(defaultManifest.getAttributes());
-        for(Map.Entry<String, Attributes> ent : defaultManifest.getSections().entrySet()) {
-            expectedManifest.attributes(ent.getValue(), ent.getKey());
-        }
-        expectedManifest.attributes(otherManifest.getAttributes());
-
-        DefaultManifest manifest = osgiManifest.getEffectiveManifest();
-        assertTrue(manifest.isEqualsTo(expectedManifest));
-    }
-
-    @Test
-    public void generateWithNull() throws Exception {
-        setUpOsgiManifest();
-        prepareMockForNullTest();
-        osgiManifest.setVersion(null);
-        osgiManifest.getEffectiveManifest();
-    }
-
-    private void setUpOsgiManifest() throws IOException {
-        final FileCollection fileCollection = context.mock(FileCollection.class);
-        context.checking(new Expectations() {{
-            allowing(fileCollection).getFiles();
-            will(returnValue(WrapUtil.toSet(new File("someFile"))));
-        }});
-        osgiManifest.setSymbolicName("symbolic");
-        osgiManifest.setName("myName");
-        osgiManifest.setVersion("myVersion");
-        osgiManifest.setDescription("myDescription");
-        osgiManifest.setLicense("myLicense");
-        osgiManifest.setVendor("myVendor");
-        osgiManifest.setDocURL("myDocUrl");
-        osgiManifest.instruction(Analyzer.EXPORT_PACKAGE, "pack1", "pack2");
-        osgiManifest.instruction(Analyzer.IMPORT_PACKAGE, "pack3", "pack4");
-        osgiManifest.setClasspath(fileCollection);
-        osgiManifest.setClassesDir(new File("someDir"));
-        addPlainAttributesAndSections(osgiManifest);
-    }
-
-    private void prepareMock() throws Exception {
-        context.checking(new Expectations() {{
-            one(analyzerMock).setProperty(Analyzer.BUNDLE_VERSION, osgiManifest.getVersion());
-        }});
-        prepareMockForNullTest();
-    }
-
-    private void prepareMockForNullTest() throws Exception {
-        context.checking(new Expectations() {{
-            one(analyzerMock).setProperty(Analyzer.BUNDLE_SYMBOLICNAME, osgiManifest.getSymbolicName());
-            one(analyzerMock).setProperty(Analyzer.BUNDLE_NAME, osgiManifest.getName());
-            one(analyzerMock).setProperty(Analyzer.BUNDLE_DESCRIPTION, osgiManifest.getDescription());
-            one(analyzerMock).setProperty(Analyzer.BUNDLE_LICENSE, osgiManifest.getLicense());
-            one(analyzerMock).setProperty(Analyzer.BUNDLE_VENDOR, osgiManifest.getVendor());
-            one(analyzerMock).setProperty(Analyzer.BUNDLE_DOCURL, osgiManifest.getDocURL());
-            one(analyzerMock).setProperty(Analyzer.EXPORT_PACKAGE, GUtil.join(osgiManifest.instructionValue(Analyzer.EXPORT_PACKAGE), ","));
-            one(analyzerMock).setProperty(Analyzer.IMPORT_PACKAGE, GUtil.join(osgiManifest.instructionValue(Analyzer.IMPORT_PACKAGE), ","));
-
-            one(analyzerMock).setProperty(ARBITRARY_ATTRIBUTE, "I like green eggs and ham.");
-
-            one(analyzerMock).setJar(osgiManifest.getClassesDir());
-            one(analyzerMock).setClasspath(osgiManifest.getClasspath().getFiles().toArray(new File[osgiManifest.getClasspath().getFiles().size()]));
-            Manifest testManifest = new Manifest();
-            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_SYMBOLICNAME, osgiManifest.getSymbolicName());
-            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_NAME, osgiManifest.getName());
-            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_DESCRIPTION, osgiManifest.getDescription());
-            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_LICENSE, osgiManifest.getLicense());
-            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_VENDOR, osgiManifest.getVendor());
-            testManifest.getMainAttributes().putValue(Analyzer.BUNDLE_DOCURL, osgiManifest.getDocURL());
-            testManifest.getMainAttributes().putValue(Analyzer.EXPORT_PACKAGE, GUtil.join(osgiManifest.instructionValue(Analyzer.EXPORT_PACKAGE), ","));
-            testManifest.getMainAttributes().putValue(Analyzer.IMPORT_PACKAGE, GUtil.join(osgiManifest.instructionValue(Analyzer.IMPORT_PACKAGE), ","));
-            allowing(analyzerMock).calcManifest();
-            will(returnValue(testManifest));
-        }});
-    }
-
-    private DefaultManifest getDefaultManifestWithOsgiValues() {
-        DefaultManifest manifest = new DefaultManifest(fileResolver);
-        manifest.getAttributes().put(Analyzer.BUNDLE_SYMBOLICNAME, osgiManifest.getSymbolicName());
-        manifest.getAttributes().put(Analyzer.BUNDLE_NAME, osgiManifest.getName());
-        manifest.getAttributes().put(Analyzer.BUNDLE_DESCRIPTION, osgiManifest.getDescription());
-        manifest.getAttributes().put(Analyzer.BUNDLE_LICENSE, osgiManifest.getLicense());
-        manifest.getAttributes().put(Analyzer.BUNDLE_VENDOR, osgiManifest.getVendor());
-        manifest.getAttributes().put(Analyzer.BUNDLE_DOCURL, osgiManifest.getDocURL());
-        manifest.getAttributes().put(Analyzer.EXPORT_PACKAGE, GUtil.join(osgiManifest.instructionValue(Analyzer.EXPORT_PACKAGE), ","));
-        manifest.getAttributes().put(Analyzer.IMPORT_PACKAGE, GUtil.join(osgiManifest.instructionValue(Analyzer.IMPORT_PACKAGE), ","));
-        addPlainAttributesAndSections(manifest);
-        return manifest;
-    }
-
-    private void addPlainAttributesAndSections(DefaultManifest manifest) {
-        manifest.getAttributes().put(ARBITRARY_ATTRIBUTE, "I like green eggs and ham.");
-        Attributes sectionAtts = new DefaultAttributes();
-        sectionAtts.put(ANOTHER_ARBITRARY_ATTRIBUTE, "Death is the great equalizer.");
-        manifest.getSections().put(ARBITRARY_SECTION, sectionAtts);
-    }
-}
diff --git a/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy b/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy
index 66c9242..90ac01a 100644
--- a/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy
+++ b/subprojects/osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy
@@ -15,13 +15,15 @@
  */
 package org.gradle.api.plugins.osgi
 
-import spock.lang.Specification
 import org.gradle.util.HelperUtil
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.internal.plugins.osgi.DefaultOsgiManifest
 import org.gradle.api.internal.plugins.osgi.OsgiHelper
 import org.gradle.api.plugins.JavaBasePlugin
 
+import spock.lang.Specification
+import spock.lang.Issue
+
 /**
  * @author Hans Dockter
  */
@@ -50,6 +52,32 @@ class OsgiPluginConventionTest extends Specification {
         osgiManifest.description == 'myDescription'
     }
 
+    @Issue("GRADLE-1670")
+    def "doesn't assume that project version is a String"() {
+        project.version =  new Object() {
+            String toString() {
+                "2.1"
+            }
+        }
+        def manifest = osgiPluginConvention.osgiManifest()
+
+        expect:
+        manifest.version == "2.1"
+    }
+
+    @Issue("GRADLE-1670")
+    def "computes its defaults lazily"() {
+        def manifest = osgiPluginConvention.osgiManifest()
+        project.version = 2.1
+        project.group = "my.group"
+        project.archivesBaseName = "myarchive"
+
+        expect:
+        manifest.version == "2.1"
+        manifest.name == "myarchive"
+        manifest.symbolicName == "my.group.myarchive"
+    }
+
     void matchesExpectedConfig(DefaultOsgiManifest osgiManifest) {
         OsgiHelper osgiHelper = new OsgiHelper();
         assert osgiManifest.version == osgiHelper.getVersion((String) project.version)
diff --git a/subprojects/performance/performance.gradle b/subprojects/performance/performance.gradle
index a384bf8..f55f7fb 100644
--- a/subprojects/performance/performance.gradle
+++ b/subprojects/performance/performance.gradle
@@ -35,6 +35,22 @@ task multiGroovy(type: ProjectGeneratorTask, description: 'Generates a multi-pro
     groovyProject = true
 }
 
+task largeMulti(type: ProjectGeneratorTask, description: 'Generates a large multi-project build') {
+    projects = 800
+    sourceFiles = 100
+}
+
+task lotDependencies(type: ProjectGeneratorTask, description: 'Generates a small multi-project build with a large Dependency Graph'){
+    projects = 25
+    sourceFiles = 100
+
+    dependencyGraph {
+        size = 200
+        depth = 4
+        useSnapshotVersions = true //default is false
+    }
+}
+
 def generators = tasks.withType(ProjectGeneratorTask)
 generators.all {
     group = 'Project setup'
diff --git a/subprojects/performance/src/generator.groovy b/subprojects/performance/src/generator.groovy
index 09e0211..1096867 100644
--- a/subprojects/performance/src/generator.groovy
+++ b/subprojects/performance/src/generator.groovy
@@ -1,5 +1,10 @@
 import groovy.text.SimpleTemplateEngine
 import groovy.text.Template
+import org.gradle.api.tasks.TaskAction
+import java.text.SimpleDateFormat
+import java.util.jar.JarOutputStream
+import java.util.jar.Manifest
+import java.util.jar.JarEntry
 
 class TestProject {
     final String name
@@ -7,9 +12,8 @@ class TestProject {
     Integer sourceFiles
     Integer testSourceFiles
     Integer linesOfCodePerSourceFile
-
-    TestProject() {
-    }
+    List<MavenModule> dependencies
+    MavenRepository repository;
 
     TestProject(String name, Object defaults) {
         this.name = name
@@ -42,6 +46,8 @@ class ProjectGeneratorTask extends DefaultTask {
     final SimpleTemplateEngine engine = new SimpleTemplateEngine()
     final Map<File, Template> templates = [:]
 
+    final DependencyGraph dependencyGraph = new DependencyGraph()
+
     def ProjectGeneratorTask() {
         outputs.upToDateWhen { false }
         setProjects(1)
@@ -63,13 +69,24 @@ class ProjectGeneratorTask extends DefaultTask {
         }
     }
 
+    void dependencyGraph(Closure configClosure) {
+        configClosure.setDelegate(dependencyGraph)
+        configClosure.setResolveStrategy(Closure.DELEGATE_ONLY)
+        configClosure.call()
+    }
+
     @TaskAction
     void generate() {
         ant.delete(dir: destDir)
         destDir.mkdirs()
 
+        MavenRepository repo = generateDependencyRepository()
         generateRootProject()
         subprojects.each {
+            if(repo){
+                it.setRepository(repo)
+                it.setDependencies(repo.getDependenciesOfTransitiveLevel(1))
+            }
             generateSubProject(it)
         }
     }
@@ -82,6 +99,15 @@ class ProjectGeneratorTask extends DefaultTask {
         return projects[0]
     }
 
+    MavenRepository generateDependencyRepository(){
+        MavenRepository repo = new RepositoryBuilder(getDestDir())
+                .withArtifacts(dependencyGraph.size)
+                .withDepth(dependencyGraph.depth)
+                .withSnapshotVersions(dependencyGraph.useSnapshotVersions)
+                .create()
+        return repo;
+    }
+
     List<TestProject> getSubprojects() {
         return projects.subList(1, projects.size())
     }
@@ -104,7 +130,7 @@ class ProjectGeneratorTask extends DefaultTask {
 
     def generateProject(Map args, TestProject testProject) {
         File projectDir = args.projectDir
-        logger.lifecycle"Generating test project '$testProject.name' into $projectDir"
+        logger.lifecycle "Generating test project '$testProject.name' into $projectDir"
 
         List files = args.files + [
                 'build.gradle',
@@ -121,7 +147,7 @@ class ProjectGeneratorTask extends DefaultTask {
             }
         }
 
-        args += [projectName: testProject.name, groovyProject: groovyProject, propertyCount: (testProject.linesOfCodePerSourceFile.intdiv(7))]
+        args += [projectName: testProject.name, groovyProject: groovyProject, propertyCount: (testProject.linesOfCodePerSourceFile.intdiv(7)), repository: testProject.repository, dependencies:testProject.dependencies]
 
         files.each {String name ->
             generate(name, name, args)
@@ -164,4 +190,287 @@ class ProjectGeneratorTask extends DefaultTask {
 }
 
 //workaround for referring to task types defined in plugin scripts
-project.ext.set('ProjectGeneratorTask', ProjectGeneratorTask)
\ No newline at end of file
+project.ext.set('ProjectGeneratorTask', ProjectGeneratorTask)
+
+class DependencyGraph {
+    int size = 0
+    int depth = 1
+    boolean useSnapshotVersions = false
+    boolean isEmpty(){
+        size==0
+    }
+}
+
+class MavenRepository {
+    int depth = 1
+    final File rootDir
+    List<MavenModule> modules = []
+
+    MavenRepository(File rootDir) {
+        println rootDir
+        this.rootDir = rootDir
+    }
+
+    URI getUri() {
+        return rootDir.toURI()
+    }
+
+    MavenModule addModule(String groupId, String artifactId, Object version = '1.0') {
+        def artifactDir = new File(rootDir, "${groupId.replace('.', '/')}/$artifactId/$version")
+        def module = new MavenModule(artifactDir, groupId, artifactId, version as String);
+        modules << module
+        return module
+    }
+
+    void publish(){
+        modules.each{
+            it.publish()
+        }
+    }
+
+    List<MavenModule> getDependenciesOfTransitiveLevel(int level){
+        return modules.findAll{((int)(it.artifactId - "artifact").toInteger() % depth) == level - 1 }
+    }
+}
+
+class MavenModule {
+    final File moduleDir
+    final String groupId
+    final String artifactId
+    final String version
+    String parentPomSection
+    String type = 'jar'
+    private final List dependencies = []
+    int publishCount = 1
+    final updateFormat = new SimpleDateFormat("yyyyMMddHHmmss")
+    final timestampFormat = new SimpleDateFormat("yyyyMMdd.HHmmss")
+    private final List artifacts = []
+    private boolean uniqueSnapshots = true;
+
+    MavenModule(File moduleDir, String groupId, String artifactId, String version) {
+        this.moduleDir = moduleDir
+        this.groupId = groupId
+        this.artifactId = artifactId
+        this.version = version
+    }
+
+    MavenModule dependsOn(String dependencyArtifactId) {
+        dependsOn(groupId, dependencyArtifactId, '1.0')
+        return this
+    }
+
+    MavenModule dependsOn(String group, String artifactId, String version) {
+        this.dependencies << [groupId: group, artifactId: artifactId, version: version]
+        return this
+    }
+
+    String shortNotation(){
+        return "$groupId:$artifactId:$version"
+    }
+
+    File getPomFile() {
+        return new File(moduleDir, "$artifactId-${publishArtifactVersion}.pom")
+    }
+
+    File artifactFile(Map<String, ?> options) {
+        def artifact = toArtifact(options)
+        def fileName = "$artifactId-${publishArtifactVersion}.${artifact.type}"
+        if (artifact.classifier) {
+            fileName = "$artifactId-$publishArtifactVersion-${artifact.classifier}.${artifact.type}"
+        }
+        return new File(moduleDir, fileName)
+    }
+
+    String getPublishArtifactVersion() {
+        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+            return "${version.replaceFirst('-SNAPSHOT$', '')}-${timestampFormat.format(publishTimestamp)}-${publishCount}"
+        }
+        return version
+    }
+
+    Date getPublishTimestamp() {
+        return new Date(updateFormat.parse("20100101120000").time + publishCount * 1000)
+    }
+
+    /**
+     * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module.
+     */
+    MavenModule publish() {
+        moduleDir.mkdirs()
+
+        if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
+            def metaDataFile = new File(moduleDir, 'maven-metadata.xml')
+            metaDataFile.text = """
+<metadata>
+  <groupId>$groupId</groupId>
+  <artifactId>$artifactId</artifactId>
+  <version>$version</version>
+  <versioning>
+    <snapshot>
+      <timestamp>${timestampFormat.format(publishTimestamp)}</timestamp>
+      <buildNumber>$publishCount</buildNumber>
+    </snapshot>
+    <lastUpdated>${updateFormat.format(publishTimestamp)}</lastUpdated>
+  </versioning>
+</metadata>
+"""
+        }
+
+        pomFile.text = ""
+        pomFile << """
+<project xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>$groupId</groupId>
+  <artifactId>$artifactId</artifactId>
+  <packaging>$type</packaging>
+  <version>$version</version>
+  <description>Published on $publishTimestamp</description>"""
+
+        if (parentPomSection) {
+            pomFile << "\n$parentPomSection\n"
+        }
+
+        dependencies.each { dependency ->
+            pomFile << """
+  <dependencies>
+    <dependency>
+      <groupId>$dependency.groupId</groupId>
+      <artifactId>$dependency.artifactId</artifactId>
+      <version>$dependency.version</version>
+    </dependency>
+  </dependencies>"""
+        }
+
+        pomFile << "\n</project>"
+
+        artifacts.each { artifact ->
+            publishArtifact(artifact)
+        }
+        publishArtifact([:])
+        return this
+    }
+
+    void createEmptyJar(File artifactFile) {
+        String content = "testcontent"
+        try {
+            FileOutputStream stream = new FileOutputStream(artifactFile);
+            JarOutputStream out = new JarOutputStream(stream, new Manifest());
+
+            // Add archive entry
+            JarEntry jarAdd = new JarEntry(artifactFile.name + ".properties");
+            jarAdd.setTime(System.currentTimeMillis());
+            out.putNextEntry(jarAdd);
+
+            // Write file to archive
+            out.write(content.getBytes("utf-8"), 0, content.getBytes("utf-8").length);
+
+            out.close();
+            stream.close();
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            System.out.println("Error: " + ex.getMessage());
+        }
+    }
+
+    private File publishArtifact(Map<String, ?> artifact) {
+        def artifactFile = artifactFile(artifact)
+        if (type != 'pom') {
+            if (type == 'jar') {
+                createEmptyJar(artifactFile)
+            } else {
+                artifactFile << "add some content so that file size isn't zero: $publishCount"
+            }
+        }
+
+        return artifactFile
+    }
+
+    private Map<String, Object> toArtifact(Map<String, ?> options) {
+        options = new HashMap<String, Object>(options)
+        def artifact = [type: options.remove('type') ?: type, classifier: options.remove('classifier') ?: null]
+        assert options.isEmpty(): "Unknown options : ${options.keySet()}"
+        return artifact
+    }
+}
+
+class MavenPom {
+    final Map<String, MavenScope> scopes = [:]
+
+    MavenPom(File pomFile) {
+        def pom = new XmlParser().parse(pomFile)
+        pom.dependencies.dependency.each { dep ->
+            def scopeElement = dep.scope
+            def scopeName = scopeElement ? scopeElement.text() : "runtime"
+            def scope = scopes[scopeName]
+            if (!scope) {
+                scope = new MavenScope()
+                scopes[scopeName] = scope
+            }
+            scope.addDependency(dep.groupId.text(), dep.artifactId.text(), dep.version.text())
+        }
+    }
+}
+
+class MavenScope {
+    final dependencies = []
+
+    void addDependency(String groupId, String artifactId, String version) {
+        dependencies << [groupId: groupId, artifactId: artifactId, version: version]
+    }
+}
+
+class RepositoryBuilder {
+    private int depth = 1
+    private int numberOfArtifacts = 0
+    private File targetDir
+    boolean withSnapshotVersions = false
+
+    public RepositoryBuilder(File targetDir) {
+        this.targetDir = targetDir;
+    }
+
+    RepositoryBuilder withArtifacts(int numberOfArtifacts) {
+        this.numberOfArtifacts = numberOfArtifacts
+        return this;
+    }
+
+    RepositoryBuilder withDepth(int depth) {
+        this.depth = depth
+        return this;
+    }
+
+    RepositoryBuilder withSnapshotVersions(boolean withSnapshotVersions) {
+        this.withSnapshotVersions = withSnapshotVersions
+        return this;
+    }
+
+    MavenRepository create() {
+        if(numberOfArtifacts==0){
+            return null;
+        }
+        targetDir.mkdirs();
+        MavenRepository repo = new MavenRepository(new File(targetDir, "mavenRepo"))
+        numberOfArtifacts.times {
+            if(withSnapshotVersions){
+                repo.addModule('group', "artifact$it", "1.0-SNAPSHOT")
+            }else{
+                repo.addModule('group', "artifact$it")
+            }
+        }
+
+        transformGraphToDepth(repo.modules, depth)
+        repo.setDepth(depth)
+        repo.publish()
+        repo
+    }
+
+    void transformGraphToDepth(List<MavenModule> modules, int depth) {
+        def depGroups = modules.groupBy { (int) (it.artifactId - "artifact").toInteger() / depth }
+        depGroups.each {idx, groupModules ->
+            for (int i = 0; i < groupModules.size() - 1; i++) {
+                def next = groupModules[i + 1]
+                groupModules[i].dependsOn(next.groupId, next.artifactId, next.version)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/performance/src/templates/build.gradle b/subprojects/performance/src/templates/build.gradle
index db0df72..9350482 100644
--- a/subprojects/performance/src/templates/build.gradle
+++ b/subprojects/performance/src/templates/build.gradle
@@ -18,35 +18,38 @@ apply plugin: 'java'
 apply plugin: 'eclipse'
 
 repositories {
-    mavenCentral()
-//    mavenRepo(urls: 'http://snapshots.repository.codehaus.org/')
-    ivy {
-        name = 'jfrog'
-        artifactPattern('http://repo.jfrog.org/artifactory/gradle-plugins-snapshots/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]')
-//        artifactPattern('http://repo.jfrog.org/artifactory/gradle-ivy-local/[organisation]/[module]/ivy-[revision].xml')
+<% if (repository) { %>
+    maven {
+        url "${repository.getUri()}"
     }
+<% } %>
+    mavenCentral()
 }
 
 dependencies {
     compile 'commons-lang:commons-lang:2.5'
     compile "commons-httpclient:commons-httpclient:3.0"
     compile "commons-codec:commons-codec:1.2"
-    compile "org.slf4j:jcl-over-slf4j:1.6.4"
-    compile "org.codehaus.groovy:groovy:1.8.4"
+    compile "org.slf4j:jcl-over-slf4j:1.6.6"
+    compile "org.codehaus.groovy:groovy:1.8.6"
     compile "commons-codec:commons-codec:1.2"
     testCompile 'junit:junit:4.8.2'
-    runtime 'com.esotericsoftware:kryo:1.03', 'com.esotericsoftware:minlog:1.2', 'com.googlecode:reflectasm:1.01'
+    runtime 'com.googlecode:reflectasm:1.01'
+
+    <% if (dependencies) { dependencies.each { %>
+    compile "${it.shortNotation()}" <% } %>
+    <% } %>
 }
 
 test {
     jvmArgs '-XX:MaxPermSize=512m', '-XX:+HeapDumpOnOutOfMemoryError'
-//    testReport = false
+    testReport = false
 }
 
 <% if (groovyProject) { %>
 apply plugin: 'groovy'
 dependencies {
-    groovy 'org.codehaus.groovy:groovy-all:1.8.4'
+    groovy 'org.codehaus.groovy:groovy:1.8.6'
 }
 <% } %>
 
diff --git a/subprojects/performance/src/templates/pom.xml b/subprojects/performance/src/templates/pom.xml
index bc00a1d..6ef4a78 100644
--- a/subprojects/performance/src/templates/pom.xml
+++ b/subprojects/performance/src/templates/pom.xml
@@ -11,15 +11,59 @@
 <% } else { %>
     <packaging>jar</packaging>
 <% } %>
-
     <version>1.0-SNAPSHOT</version>
+    <% if (repository ) { %>
+    <repositories>
+        <repository>
+          <id>local-repo</id>
+          <url>${repository.getUri()}</url>
+        </repository>
+      </repositories>
+    <% } %>
     <dependencies>
         <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-httpclient</groupId>
+            <artifactId>commons-httpclient</artifactId>
+            <version>3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <version>1.6.4</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy</artifactId>
+            <version>1.8.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode</groupId>
+            <artifactId>reflectasm</artifactId>
+            <version>1.01</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
-            <version>4.8.1</version>
+            <version>4.8.2</version>
             <scope>test</scope>
         </dependency>
+    <% if (dependencies) { dependencies.each { dep -> %>
+        <dependency>
+            <groupId>${dep.groupId}</groupId>
+            <artifactId>${dep.artifactId}</artifactId>
+            <version>${dep.version}</version>
+        </dependency> <% } %>  <% } %>
     </dependencies>
     <build>
         <plugins>
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
new file mode 100644
index 0000000..893f01a
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 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 org.gradle.api.plugins
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+
+class BasePluginIntegrationTest extends AbstractIntegrationSpec {
+
+    @Requires(TestPrecondition.MANDATORY_FILE_LOCKING)
+    def "clean failure message indicates file"() {
+        given:
+        executer.mustFork = true
+        buildFile << """
+            apply plugin: 'base'
+        """
+
+        and:
+        def lock = new RandomAccessFile(file("build/newFile").createFile(), "rw").channel.lock()
+
+        when:
+        fails "clean"
+
+        then:
+        failure.assertHasCause("Unable to delete file")
+
+        cleanup:
+        lock?.release()
+    }
+
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy
index b585370..a27053e 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/reporting/internal/TaskReportContainerIntegTest.groovy
@@ -41,7 +41,7 @@ class TaskReportContainerIntegTest extends AbstractIntegrationSpec {
 
             class TestTask extends DefaultTask {
                 @Nested
-                TaskReportContainer reports = project.services.get(org.gradle.api.internal.Instantiator).newInstance(TestTaskReportContainer, this)
+                TaskReportContainer reports = project.services.get(org.gradle.internal.reflect.Instantiator).newInstance(TestTaskReportContainer, this)
 
                 @TaskAction
                 def doStuff() {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/JarIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/JarIntegrationTest.groovy
new file mode 100644
index 0000000..2a7c872
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/JarIntegrationTest.groovy
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks.bundling
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import static org.hamcrest.Matchers.equalTo
+
+class JarIntegrationTest extends AbstractIntegrationSpec {
+
+    def canCreateAnEmptyJar() {
+        given:
+        buildFile << """
+                task jar(type: Jar) {
+                    from 'test'
+                    destinationDir = buildDir
+                    archiveName = 'test.jar'
+        }
+        """
+        when:
+        run 'jar'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.jar').unzipTo(expandDir)
+        expandDir.assertHasDescendants('META-INF/MANIFEST.MF')
+        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
+    }
+
+    def canCreateAJarArchiveWithDefaultManifest() {
+        given:
+        createDir('test') {
+            dir1 {
+                file 'file1.txt'
+            }
+        }
+        createDir('meta-inf') {
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+            }
+        }
+        and:
+        buildFile << """
+            task jar(type: Jar) {
+                from 'test'
+                metaInf {
+                    from 'meta-inf'
+                }
+                destinationDir = buildDir
+                archiveName = 'test.jar'
+            }
+        """
+        when:
+        run 'jar'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.jar').unzipTo(expandDir)
+        expandDir.assertHasDescendants('META-INF/MANIFEST.MF', 'META-INF/file1.txt', 'META-INF/dir2/file2.txt', 'dir1/file1.txt')
+        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
+    }
+
+    def metaInfSpecsAreIndependentOfOtherSpec() {
+        given:
+        createDir('test') {
+            dir1 {
+                file 'ignored.xml'
+                file 'file1.txt'
+            }
+        }
+        createDir('meta-inf') {
+            dir2 {
+                file 'ignored.txt'
+                file 'file2.xml'
+            }
+        }
+        createDir('meta-inf2') {
+            file 'file2.txt'
+            file 'file2.xml'
+        }
+        and:
+        buildFile << """
+            task jar(type: Jar) {
+                from 'test'
+                include '**/*.txt'
+                metaInf {
+                    from 'meta-inf'
+                    include '**/*.xml'
+                }
+                metaInf {
+                    from 'meta-inf2'
+                    into 'dir3'
+                }
+                destinationDir = buildDir
+                archiveName = 'test.jar'
+            }
+        """
+        when:
+        run 'jar'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.jar').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'META-INF/dir2/file2.xml',
+                'META-INF/dir3/file2.txt',
+                'META-INF/dir3/file2.xml',
+                'dir1/file1.txt')
+    }
+
+    def usesManifestFromJarTaskWhenMergingJars() {
+        given:
+        createDir('src1') {
+            dir1 { file 'file1.txt' }
+        }
+        createDir('src2') {
+            dir2 { file 'file2.txt' }
+        }
+        buildFile << '''
+            task jar1(type: Jar) {
+                from 'src1'
+                destinationDir = buildDir
+                archiveName = 'test1.zip'
+                manifest { attributes(attr: 'jar1') }
+            }
+            task jar2(type: Jar) {
+                from 'src2'
+                destinationDir = buildDir
+                archiveName = 'test2.zip'
+                manifest { attributes(attr: 'jar2') }
+            }
+            task jar(type: Jar) {
+                dependsOn jar1, jar2
+                from zipTree(jar1.archivePath), zipTree(jar2.archivePath)
+                manifest { attributes(attr: 'value') }
+                destinationDir = buildDir
+                archiveName = 'test.zip'
+            }
+            '''
+        when:
+        run 'jar'
+        then:
+        def jar = file('build/test.zip')
+        def manifest = jar.manifest
+        manifest.mainAttributes.getValue('attr') == 'value'
+
+        def expandDir = file('expected')
+        jar.unzipTo(expandDir)
+        expandDir.assertHasDescendants('dir1/file1.txt', 'dir2/file2.txt', 'META-INF/MANIFEST.MF')
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/WarTaskIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/WarTaskIntegrationTest.groovy
new file mode 100644
index 0000000..11354d7
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/tasks/bundling/WarTaskIntegrationTest.groovy
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks.bundling
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import static org.hamcrest.Matchers.equalTo
+
+class WarTaskIntegrationTest extends AbstractIntegrationSpec {
+
+    def canCreateAWarArchiveWithNoWebXml() {
+        given:
+        createDir('content') {
+            content1 {
+                file 'file1.jsp'
+            }
+        }
+        createDir('web-inf') {
+            webinf1 {
+                file 'file1.txt'
+            }
+        }
+        createDir('meta-inf') {
+            metainf1 {
+                file 'file2.txt'
+            }
+        }
+        createDir('classes') {
+            org {
+                gradle {
+                    file 'resource.txt'
+                    file 'Person.class'
+                }
+            }
+        }
+        createZip("lib.jar") {
+            file "Dependency.class"
+        }
+        and:
+        buildFile << """
+            task war(type: War) {
+                from 'content'
+                metaInf {
+                    from 'meta-inf'
+                }
+                webInf {
+                    from 'web-inf'
+                }
+                classpath 'classes'
+                classpath 'lib.jar'
+                destinationDir = buildDir
+                archiveName = 'test.war'
+            }
+        """
+        when:
+        run "war"
+        then:
+        def expandDir = file('expanded')
+        file('build/test.war').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'META-INF/metainf1/file2.txt',
+                'content1/file1.jsp',
+                'WEB-INF/lib/lib.jar',
+                'WEB-INF/classes/org/gradle/resource.txt',
+                'WEB-INF/classes/org/gradle/Person.class',
+                'WEB-INF/webinf1/file1.txt')
+
+        expandDir.file('META-INF/MANIFEST.MF').assertContents(equalTo('Manifest-Version: 1.0\r\n\r\n'))
+    }
+
+    def canCreateAWarArchiveWithWebXml() {
+        given: file('some.xml') << '<web/>'
+        createDir('web-inf') {
+            webinf1 {
+                file 'file1.txt'
+            }
+        }
+        and:
+        buildFile << """
+            task war(type: War) {
+                webInf {
+                    from 'web-inf'
+                    exclude '**/*.xml'
+                }
+                webXml = file('some.xml')
+                destinationDir = buildDir
+                archiveName = 'test.war'
+            }
+        """
+        when:
+        run "war"
+        then:
+        def expandDir = file('expanded')
+        file('build/test.war').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'WEB-INF/web.xml',
+                'WEB-INF/webinf1/file1.txt')
+    }
+
+    def canAddFilesToWebInfDir() {
+        given:
+        createDir('web-inf') {
+            webinf1 {
+                file 'file1.txt'
+                file 'ignore.xml'
+            }
+        }
+        createDir('web-inf2') {
+            file 'file2.txt'
+        }
+        and:
+        buildFile << """
+            task war(type: War) {
+                webInf {
+                    from 'web-inf'
+                    exclude '**/*.xml'
+                }
+                webInf {
+                    from 'web-inf2'
+                    into 'dir2'
+                    include '**/file2*'
+                }
+                destinationDir = buildDir
+                archiveName = 'test.war'
+            }
+        """
+        when:
+        run 'war'
+        then:
+        def expandDir = file('expanded')
+        file('build/test.war').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'WEB-INF/webinf1/file1.txt',
+                'WEB-INF/dir2/file2.txt')
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
index 164ab2a..5867058 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntForkingGroovyCompilerIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.integtests.fixtures.TargetVersions
 class AntForkingGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
 
     @Override
-    def String compilerConfiguration() {
+    String compilerConfiguration() {
         '''
     tasks.withType(GroovyCompile) {
         groovyOptions.useAnt = true
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
new file mode 100644
index 0000000..e477d83
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 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 org.gradle.groovy.compile
+
+import org.gradle.integtests.fixtures.JUnitTestExecutionResult
+
+abstract class ApiGroovyCompilerIntegrationSpec extends GroovyCompilerIntegrationSpec {
+    def canEnableAndDisableIntegerOptimization() {
+        if (versionLowerThan('1.8')) {
+            return
+        }
+
+        when:
+        run("sanityCheck")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def canEnableAndDisableAllOptimizations() {
+        if (versionLowerThan('1.8')) {
+            return
+        }
+
+        when:
+        run("sanityCheck")
+
+        then:
+        noExceptionThrown()
+    }
+
+    def canUseCustomFileExtensions() {
+        if (versionLowerThan('1.7')) {
+            return
+        }
+
+        when:
+        run("test")
+
+        then:
+        noExceptionThrown()
+        def result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted("Person")
+        result.testClass("Person").assertTestPassed("testMe")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
index 5019dea..c1decd7 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
@@ -21,8 +21,9 @@ import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.integtests.fixtures.ExecutionFailure
+import com.google.common.collect.Ordering
 
- at TargetVersions(['1.5.8', '1.6.9', '1.7.10', '1.8.6'])
+ at TargetVersions(['1.5.8', '1.6.9', '1.7.10', '1.8.6', '2.0.0'])
 abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegrationSpec {
     @Rule TestResources resources = new TestResources()
 
@@ -35,8 +36,12 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
         runAndFail("classes")
 
         then:
-        compileErrorOutput.contains 'unable to resolve class Unknown1'
-        compileErrorOutput.contains 'unable to resolve class Unknown2'
+        // for some reasons, line breaks occur in different places when running this
+        // test in different environments; hence we only check for short snippets
+        compileErrorOutput.contains 'unable'
+        compileErrorOutput.contains 'resolve'
+        compileErrorOutput.contains 'Unknown1'
+        compileErrorOutput.contains 'Unknown2'
         failure.assertHasCause(compilationFailureMessage)
     }
 
@@ -57,6 +62,18 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
         noExceptionThrown()
     }
 
+    def "canListSourceFiles"() {
+        when:
+        run("compileGroovy")
+
+        then:
+        output.contains(new File("src/main/groovy/compile/test/Person.groovy").toString())
+        output.contains(new File("src/main/groovy/compile/test/Person2.groovy").toString())
+        !errorOutput
+        file("build/classes/main/compile/test/Person.class").exists()
+        file("build/classes/main/compile/test/Person2.class").exists()
+    }
+
     @Override
     protected ExecutionResult run(String... tasks) {
         tweakBuildFile()
@@ -70,11 +87,22 @@ abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegratio
     }
 
     private void tweakBuildFile() {
-        buildFile << """
-dependencies { groovy 'org.codehaus.groovy:groovy:$version' }
-"""
-        buildFile << compilerConfiguration()
+        if (version == "2.0.0") {
+            // groovy-all-2.0.0 isn't compatible with JDK 1.5
+            // see GROOVY-5591
+            buildFile << """
+dependencies {
+    groovy 'org.codehaus.groovy:groovy:$version'
+    groovy 'org.codehaus.groovy:groovy-test:$version'
+}
+            """
+        } else {
+            buildFile << """
+dependencies { groovy 'org.codehaus.groovy:groovy-all:$version' }
+            """
+        }
 
+        buildFile << compilerConfiguration()
         println "->> USING BUILD FILE: ${buildFile.text}"
     }
 
@@ -87,4 +115,15 @@ dependencies { groovy 'org.codehaus.groovy:groovy:$version' }
     String getCompileErrorOutput() {
         return errorOutput
     }
+
+    boolean versionLowerThan(String other) {
+        compareToVersion(other) < 0
+    }
+
+    int compareToVersion(String other) {
+        def versionParts = version.split("\\.") as List
+        def otherParts = other.split("\\.") as List
+        def ordering = Ordering.<Integer>natural().lexicographical()
+        ordering.compare(versionParts, otherParts)
+    }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy
index ac93333..5de4e6b 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/DaemonGroovyCompilerIntegrationTest.groovy
@@ -15,10 +15,10 @@
  */
 package org.gradle.groovy.compile
 
-class DaemonGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
+class DaemonGroovyCompilerIntegrationTest extends ApiGroovyCompilerIntegrationSpec {
 
     @Override
-    def String compilerConfiguration() {
+    String compilerConfiguration() {
 '''
     tasks.withType(GroovyCompile) {
         groovyOptions.useAnt = false
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy
index f8bdbd2..dbd8630 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec.groovy
@@ -15,9 +15,11 @@
  */
 package org.gradle.groovy.compile
 
+import spock.lang.Issue
+
 abstract class GroovyCompilerIntegrationSpec extends BasicGroovyCompilerIntegrationSpec {
     def "canUseBuiltInAstTransform"() {
-        if (version.startsWith('1.5.')) {
+        if (versionLowerThan('1.6')) {
             return
         }
 
@@ -29,7 +31,7 @@ abstract class GroovyCompilerIntegrationSpec extends BasicGroovyCompilerIntegrat
     }
 
     def "canUseThirdPartyAstTransform"() {
-        if (version.startsWith('1.5.')) {
+        if (versionLowerThan('1.6')) {
             return
         }
 
@@ -41,7 +43,7 @@ abstract class GroovyCompilerIntegrationSpec extends BasicGroovyCompilerIntegrat
     }
 
     def "canUseAstTransformWrittenInGroovy"() {
-        if (version.startsWith('1.5.')) {
+        if (versionLowerThan('1.6')) {
             return
         }
 
@@ -52,4 +54,18 @@ abstract class GroovyCompilerIntegrationSpec extends BasicGroovyCompilerIntegrat
         noExceptionThrown()
     }
 
+    // more generally, this test is about transforms that statically reference
+    // a class from the Groovy (compiler) Jar that in turn references a class from another Jar
+    @Issue("GRADLE-2317")
+    def canUseAstTransformThatReferencesGroovyTestCase() {
+        if (versionLowerThan('1.6')) {
+            return
+        }
+
+        when:
+        run("test")
+
+        then:
+        noExceptionThrown()
+    }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy
index d108739..1acfe67 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InProcessGroovyCompilerIntegrationTest.groovy
@@ -15,9 +15,9 @@
  */
 package org.gradle.groovy.compile
 
-class InProcessGroovyCompilerIntegrationTest extends GroovyCompilerIntegrationSpec {
+class InProcessGroovyCompilerIntegrationTest extends ApiGroovyCompilerIntegrationSpec {
 
-    def String compilerConfiguration() {
+    String compilerConfiguration() {
 '''
     tasks.withType(GroovyCompile) {
         groovyOptions.useAnt = false
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy
new file mode 100644
index 0000000..053b200
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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 org.gradle.groovy.compile
+
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import org.gradle.integtests.fixtures.TargetVersions
+
+ at Requires(TestPrecondition.JDK7)
+ at TargetVersions(['2.0.0-beta-3:indy'])
+class InvokeDynamicGroovyCompilerSpec extends ApiGroovyCompilerIntegrationSpec {
+    def canEnableAndDisableInvokeDynamicOptimization() {
+        when:
+        run("sanityCheck")
+
+        then:
+        noExceptionThrown()
+    }
+
+    String compilerConfiguration() {
+        '''
+tasks.withType(GroovyCompile) {
+    groovyOptions.useAnt = false
+    groovyOptions.fork = false
+}
+        '''
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy
new file mode 100644
index 0000000..d4d5a6f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/environment/JreJavaHomeGroovyIntegrationTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2012 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 org.gradle.groovy.environment
+
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.IgnoreIf
+import spock.lang.Unroll
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+class JreJavaHomeGroovyIntegrationTest extends AbstractIntegrationSpec {
+
+    @IgnoreIf({ AvailableJavaHomes.bestJreAlternative == null})
+    @Unroll
+    def "groovy java cross compilation works in forking mode = #forkMode and useAnt = #useAnt when JAVA_HOME is set to JRE"() {
+        given:
+        def jreJavaHome = AvailableJavaHomes.bestJreAlternative
+        writeJavaTestSource("src/main/groovy")
+        writeGroovyTestSource("src/main/groovy")
+        file('build.gradle') << """
+                println "Used JRE: ${jreJavaHome.absolutePath.replace(File.separator, '/')}"
+                apply plugin:'groovy'
+                dependencies{
+                    groovy localGroovy()
+                }
+                compileGroovy{
+                    options.fork = ${forkMode}
+                    options.useAnt = ${useAnt}
+                    groovyOptions.useAnt = ${useAnt}
+                }
+                """
+        when:
+        executer.withEnvironmentVars("JAVA_HOME": jreJavaHome.absolutePath).withTasks("compileGroovy").run().output
+        then:
+        file("build/classes/main/org/test/JavaClazz.class").exists()
+        file("build/classes/main/org/test/GroovyClazz.class").exists()
+
+        where:
+        forkMode << [false, true, false]
+        useAnt << [false, false, true]
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "groovy compiler works when gradle is started with no JAVA_HOME defined in forking mode = #forkMode and useAnt = #useAnt"() {
+        given:
+        writeJavaTestSource("src/main/groovy")
+        writeGroovyTestSource("src/main/groovy")
+        file('build.gradle') << """
+            apply plugin:'groovy'
+            dependencies{
+                groovy localGroovy()
+            }
+            compileGroovy{
+                options.fork = ${forkMode}
+                options.useAnt = ${useAnt}
+                groovyOptions.useAnt = ${useAnt}
+            }
+            """
+        when:
+        def envVars = System.getenv().findAll { it.key != 'JAVA_HOME' || it.key != 'Path'}
+        envVars.put("Path", "C:\\Windows\\System32")
+        executer.withEnvironmentVars(envVars).withTasks("compileGroovy").run()
+
+        then:
+        file("build/classes/main/org/test/JavaClazz.class").exists()
+        file("build/classes/main/org/test/GroovyClazz.class").exists()
+        where:
+        forkMode << [false, true, false]
+        useAnt << [false, false, true]
+    }
+
+    private writeJavaTestSource(String srcDir, String clazzName = "JavaClazz") {
+        file(srcDir, "org/test/${clazzName}.java") << """
+            package org.test;
+            public class ${clazzName} {
+                public static void main(String... args){
+
+                }
+            }
+            """
+    }
+
+    private writeGroovyTestSource(String srcDir) {
+        file(srcDir, 'org/test/GroovyClazz.groovy') << """
+            package org.test
+            class GroovyClazz{
+                def property = "a property"
+            }
+            """
+    }
+
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy
index 129a4a4..e004f1f 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaPluginGoodBehaviourTest.groovy
@@ -15,7 +15,7 @@
  */
 package org.gradle.java
 
-import org.gradle.integtests.fixtures.*
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
 
 class JavaPluginGoodBehaviourTest extends WellBehavedPluginTest {
     @Override
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy
new file mode 100644
index 0000000..35507c3
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/environment/JreJavaHomeJavaIntegrationTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 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 org.gradle.java.environment
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.IgnoreIf
+import spock.lang.Unroll
+
+class JreJavaHomeJavaIntegrationTest extends AbstractIntegrationSpec {
+
+    @IgnoreIf({ AvailableJavaHomes.bestJreAlternative == null})
+    @Unroll
+    def "java compilation works in forking mode = #forkMode and useAnt = #useAnt when JAVA_HOME is set to JRE"() {
+        given:
+        def jreJavaHome = AvailableJavaHomes.bestJreAlternative
+        writeJavaTestSource("src/main/java");
+        file('build.gradle') << """
+        println "Used JRE: ${jreJavaHome.absolutePath.replace(File.separator, '/')}"
+        apply plugin:'java'
+        compileJava{
+            options.fork = ${forkMode}
+            options.useAnt = ${useAnt}
+        }
+        """
+        when:
+        executer.withEnvironmentVars("JAVA_HOME": jreJavaHome.absolutePath).withTasks("compileJava").run().output
+        then:
+        file("build/classes/main/org/test/JavaClazz.class").exists()
+
+        where:
+        forkMode << [false, true, false]
+        useAnt << [false, false, true]
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "java compilation works in forking mode = #forkMode and useAnt = #useAnt when gradle is started with no JAVA_HOME defined"() {
+        given:
+        writeJavaTestSource("src/main/java");
+        file('build.gradle') << """
+                    apply plugin:'java'
+                    compileJava{
+                        options.fork = ${forkMode}
+                        options.useAnt = ${useAnt}
+        }
+        """
+        def envVars = System.getenv().findAll { it.key != 'JAVA_HOME' || it.key != 'Path'}
+        envVars.put("Path", "C:\\Windows\\System32")
+        when:
+        executer.withEnvironmentVars(envVars).withTasks("compileJava").run()
+        then:
+        file("build/classes/main/org/test/JavaClazz.class").exists()
+        where:
+            forkMode << [false, true, false]
+            useAnt << [false, false, true]
+    }
+
+    private writeJavaTestSource(String srcDir) {
+        file(srcDir, 'org/test/JavaClazz.java') << """
+            package org.test;
+            public class JavaClazz {
+                public static void main(String... args){
+
+                }
+            }
+            """
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/InterruptedTestThreadIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/InterruptedTestThreadIntegrationTest.groovy
deleted file mode 100644
index 0e0cc8a..0000000
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/InterruptedTestThreadIntegrationTest.groovy
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.testing
-
-import org.gradle.integtests.fixtures.*
-
-import spock.lang.*
-
- at Issue("http://issues.gradle.org/browse/GRADLE-1948")
-class InterruptedTestThreadIntegrationTest extends AbstractIntegrationSpec {
-
-    @Timeout(30)
-    def "test interrupting its own thread does not kill test execution"() {
-        given:
-        buildFile << """
-            apply plugin: 'java'
-            repositories { mavenCentral() }
-            dependencies { testCompile "junit:junit:4.8.2" }
-        """
-        
-        and:
-        file("src/test/java/SomeTest.java") << """
-            import org.junit.*;
-
-            public class SomeTest {
-                @Test public void foo() {
-                    Thread.currentThread().interrupt();
-                }
-            }
-        """
-        
-        when:
-        run "test"
-        
-        then:
-        ":test" in nonSkippedTasks
-    }  
- 
-}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
index fe6465b..d74de2f 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
@@ -19,12 +19,18 @@ import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 import org.junit.Test
+import org.junit.Before
 import spock.lang.Issue
 
 @Issue("GRADLE-1009")
 public class TestOutputListenerIntegrationTest extends AbstractIntegrationSpec {
     @Rule public final TestResources resources = new TestResources()
 
+    @Before
+    public void before() {
+        executer.allowExtraLogging = false
+    }
+
     @Test
     def "can use standard output listener for tests"() {
         given:
@@ -187,16 +193,16 @@ test {
     testLogging.showStandardStreams = true
 }
 """
-        when: "run without '-i'"
-        def result = executer.setAllowExtraLogging(false).withTasks('test').run()
+        when: "run with quiet"
+        def result = executer.withArguments("-q").withTasks('test'). run()
         then:
         !result.output.contains('output from foo')
 
-        when: "run with '-i'"
-        result = executer.withTasks('cleanTest', 'test').withArguments('-i').run()
+        when: "run with lifecycle"
+        result = executer.setAllowExtraLogging(false).withTasks('cleanTest', 'test').run()
 
         then:
         result.output.contains('output from foo')
-        result.error.contains('error from foo')
+        result.output.contains('error from foo')
     }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
new file mode 100644
index 0000000..5c84702
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2011 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 org.gradle.testing
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import spock.lang.Issue
+import spock.lang.Timeout
+import spock.lang.Unroll
+
+/**
+ * General tests for the JVM testing infrastructure that don't deserve their own test class.
+ */
+class TestingIntegrationTest extends AbstractIntegrationSpec {
+
+    @Timeout(30)
+    @Issue("http://issues.gradle.org/browse/GRADLE-1948")
+    def "test interrupting its own thread does not kill test execution"() {
+        given:
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile "junit:junit:4.8.2" }
+        """
+        
+        and:
+        file("src/test/java/SomeTest.java") << """
+            import org.junit.*;
+
+            public class SomeTest {
+                @Test public void foo() {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        """
+        
+        when:
+        run "test"
+        
+        then:
+        ":test" in nonSkippedTasks
+    }
+
+    @Issue("http://issues.gradle.org/browse/GRADLE-2313")
+    @Unroll
+    "can clean test after extracting class file with #framework"() {
+        when:
+        buildFile << """
+            apply plugin: "java"
+            repositories.mavenCentral()
+            dependencies { testCompile "$dependency" }
+            test { $framework() }
+        """
+        and:
+        file("src/test/java/SomeTest.java") << """
+            public class SomeTest extends $superClass {
+            }
+        """
+        then:
+        succeeds "clean", "test"
+
+        and:
+        file("build/tmp/test").exists() // ensure we extracted classes
+
+        where:
+        framework   | dependency                | superClass
+        "useJUnit"  | "junit:junit:4.10"        | "org.junit.runner.Result"
+        "useTestNG" | "org.testng:testng:6.3.1" | "org.testng.Converter"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
index ad05c10..a6fd905 100755
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
@@ -22,10 +22,16 @@ import org.gradle.integtests.fixtures.*
 import static org.gradle.util.Matchers.containsLine
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
+import org.junit.Before
 
 public class JUnitIntegrationTest extends AbstractIntegrationTest {
     @Rule public final TestResources resources = new TestResources()
 
+    @Before
+    public void before() {
+        executer.allowExtraLogging = false
+    }
+
     @Test
     public void executesTestsInCorrectEnvironment() {
         executer.withTasks('build').run();
@@ -126,16 +132,6 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         failure.assertHasDescription("Execution failed for task ':test'.");
         failure.assertThatCause(startsWith('There were failing tests.'));
 
-        assert containsLine(failure.getError(), 'Test org.gradle.BrokenTest FAILED');
-        assert containsLine(failure.getError(), 'Test org.gradle.BrokenBefore FAILED');
-        assert containsLine(failure.getError(), 'Test org.gradle.BrokenAfter FAILED');
-        assert containsLine(failure.getError(), 'Test org.gradle.BrokenBeforeAndAfter FAILED');
-        assert containsLine(failure.getError(), 'Test org.gradle.BrokenBeforeClass FAILED');
-        assert containsLine(failure.getError(), 'Test org.gradle.BrokenAfterClass FAILED');
-        assert containsLine(failure.getError(), 'Test org.gradle.BrokenConstructor FAILED');
-        assert containsLine(failure.getError(), 'Test org.gradle.BrokenException FAILED');
-        assert containsLine(failure.getError(), 'Test org.gradle.Unloadable FAILED');
-
         JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
         result.assertTestClassesExecuted(
                 'org.gradle.ClassWithBrokenRunner',
@@ -353,8 +349,8 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         '''
 
         ExecutionResult result = executer.withTasks("test").run();
-        assert containsLine(result.getOutput(), "START [tests] []");
-        assert containsLine(result.getOutput(), "FINISH [tests] [] [FAILURE] [4]");
+        assert containsLine(result.getOutput(), "START [tests] [Test Run]");
+        assert containsLine(result.getOutput(), "FINISH [tests] [Test Run] [FAILURE] [4]");
 
         assert containsLine(result.getOutput(), "START [test process 'Gradle Worker 1'] [Gradle Worker 1]");
         assert containsLine(result.getOutput(), "FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1] [FAILURE] [4]");
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
new file mode 100644
index 0000000..e2bfe6e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 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 org.gradle.testing.junit
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.util.TextUtil
+import org.junit.Rule
+
+// cannot make assumptions about order in which test methods of JUnit4Test get executed
+class JUnitLoggingIntegrationTest extends AbstractIntegrationSpec {
+    @Rule TestResources resources
+    ExecutionResult result
+
+    def setup() {
+        executer.setAllowExtraLogging(false).withStackTraceChecksDisabled().withTasks("test")
+    }
+
+    def "defaultLifecycleLogging"() {
+        when:
+        result = executer.runWithFailure()
+
+        then:
+        outputContains("""
+org.gradle.JUnit4Test > badTest FAILED
+    java.lang.RuntimeException at JUnit4Test.groovy:44
+        """)
+    }
+
+    def "customQuietLogging"() {
+        when:
+        result = executer.withArguments("-q").runWithFailure()
+
+        then:
+        outputContains("""
+badTest FAILED
+    java.lang.RuntimeException: bad
+        at org.gradle.JUnit4Test.beBad(JUnit4Test.groovy:44)
+        at org.gradle.JUnit4Test.badTest(JUnit4Test.groovy:28)
+        """)
+
+        outputContains("ignoredTest SKIPPED")
+
+        outputContains("org.gradle.JUnit4Test FAILED")
+    }
+
+    def "standardOutputLogging"() {
+        when:
+        result = executer.withArguments("-q").runWithFailure()
+
+        then:
+        outputContains("""
+org.gradle.JUnit4StandardOutputTest > printTest STANDARD_OUT
+    line 1
+    line 2
+    line 3
+        """)
+    }
+
+    private void outputContains(String text) {
+        assert result.output.contains(TextUtil.toPlatformLineSeparators(text.trim()))
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
index 37de851..063b851 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.testing.testng
 
 import org.junit.Rule
 import org.junit.Test
+import org.junit.Before
 import spock.lang.Issue
 import org.gradle.integtests.fixtures.*
 import static org.gradle.util.Matchers.containsLine
@@ -31,6 +32,11 @@ class TestNGIntegrationTest {
     @Rule public GradleDistributionExecuter executer = new GradleDistributionExecuter()
     @Rule public TestResources resources = new TestResources()
 
+    @Before
+    public void before() {
+        executer.allowExtraLogging = false
+    }
+
     @Test
     void executesTestsInCorrectEnvironment() {
         ExecutionResult result = executer.withTasks('test').run();
@@ -46,8 +52,8 @@ class TestNGIntegrationTest {
     void canListenForTestResults() {
         ExecutionResult result = executer.withTasks("test").run();
 
-        assert containsLine(result.getOutput(), "START [tests] []");
-        assert containsLine(result.getOutput(), "FINISH [tests] []");
+        assert containsLine(result.getOutput(), "START [tests] [Test Run]");
+        assert containsLine(result.getOutput(), "FINISH [tests] [Test Run]");
         assert containsLine(result.getOutput(), "START [test process 'Gradle Worker 1'] [Gradle Worker 1]");
         assert containsLine(result.getOutput(), "FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1]");
         assert containsLine(result.getOutput(), "START [test 'Gradle test'] [Gradle test]");
@@ -106,10 +112,6 @@ class TestNGIntegrationTest {
         result.testClass('org.gradle.BrokenAfterSuite').assertConfigMethodFailed('cleanup')
         result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestFailed('broken', equalTo('broken'))
         result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestSkipped('okTest')
-        assertThat(execution.error, containsString('Test org.gradle.BadTest FAILED'))
-        assertThat(execution.error, containsString('Test org.gradle.TestWithBrokenSetup FAILED'))
-        assertThat(execution.error, containsString('Test org.gradle.BrokenAfterSuite FAILED'))
-        assertThat(execution.error, containsString('Test org.gradle.TestWithBrokenMethodDependency FAILED'))
     }
 
     @Issue("GRADLE-1532")
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
new file mode 100644
index 0000000..f655fb9
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012 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 org.gradle.testing.testng
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.util.TextUtil
+import org.junit.Rule
+
+// can make assumptions about order in which test methods of TestNGTest get executed
+// because the methods are chained with 'methodDependsOn'
+class TestNGLoggingIntegrationTest extends AbstractIntegrationSpec {
+    @Rule TestResources resources
+    ExecutionResult result
+
+    def setup() {
+        executer.setAllowExtraLogging(false).withStackTraceChecksDisabled().withTasks("test")
+    }
+
+    def "defaultLifecycleLogging"() {
+        when:
+        result = executer.runWithFailure()
+
+        then:
+        outputContains("""
+Gradle test > org.gradle.TestNGTest.badTest FAILED
+    java.lang.RuntimeException at TestNGTest.groovy:40
+        """)
+    }
+
+    def customQuietLogging() {
+        when:
+        result = executer.withArguments("-q").runWithFailure()
+
+        then:
+        outputContains("""
+org.gradle.TestNGTest.badTest FAILED
+    java.lang.RuntimeException: bad
+        at org.gradle.TestNGTest.beBad(TestNGTest.groovy:40)
+        at org.gradle.TestNGTest.badTest(TestNGTest.groovy:27)
+
+org.gradle.TestNGTest.ignoredTest SKIPPED
+
+Gradle test FAILED
+        """)
+    }
+
+    def "standardOutputLogging"() {
+        when:
+        result = executer.withArguments("-q").runWithFailure()
+
+        then:
+        outputContains("""
+Gradle test > org.gradle.TestNGStandardOutputTest.printTest STANDARD_OUT
+    line 1
+    line 2
+    line 3
+        """)
+    }
+
+    private void outputContains(String text) {
+        assert result.output.contains(TextUtil.toPlatformLineSeparators(text.trim()))
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableAllOptimizations/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableAllOptimizations/build.gradle
new file mode 100644
index 0000000..8678b0f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableAllOptimizations/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+task compileWithOptimization(type: GroovyCompile) {
+    source = sourceSets.main.groovy
+    classpath = configurations.compile
+    destinationDir = file("$sourceSets.main.output.classesDir/optimized")
+    groovyOptions.optimizationOptions.all = true
+}
+
+task compileWithoutOptimization(type: GroovyCompile) {
+    source = sourceSets.main.groovy
+    classpath = configurations.compile
+    destinationDir = file("$sourceSets.main.output.classesDir/unoptimized")
+    groovyOptions.optimizationOptions.all = false
+}
+
+task sanityCheck(dependsOn: [compileWithOptimization, compileWithoutOptimization]) << {
+    assert fileTree(compileWithOptimization.destinationDir).singleFile.size() != fileTree(compileWithoutOptimization.destinationDir).singleFile.size()
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableAllOptimizations/src/main/groovy/IntegerCalculations.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableAllOptimizations/src/main/groovy/IntegerCalculations.groovy
new file mode 100644
index 0000000..97747ee
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableAllOptimizations/src/main/groovy/IntegerCalculations.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.
+ */
+
+class IntegerCalculations {
+    def calculate() {
+        int a = 9834
+        int b = 238923
+        int c = a + b
+        int d = c - a
+        int e = d * c
+        int f = e.intdiv(5)
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableIntegerOptimization/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableIntegerOptimization/build.gradle
new file mode 100644
index 0000000..3b15921
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableIntegerOptimization/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+task compileWithOptimization(type: GroovyCompile) {
+    source = sourceSets.main.groovy
+    classpath = configurations.compile
+    destinationDir = file("$sourceSets.main.output.classesDir/optimized")
+    groovyOptions.optimizationOptions["int"] = true
+}
+
+task compileWithoutOptimization(type: GroovyCompile) {
+    source = sourceSets.main.groovy
+    classpath = configurations.compile
+    destinationDir = file("$sourceSets.main.output.classesDir/unoptimized")
+    groovyOptions.optimizationOptions["int"] = false
+}
+
+task sanityCheck(dependsOn: [compileWithOptimization, compileWithoutOptimization]) << {
+    assert fileTree(compileWithOptimization.destinationDir).singleFile.size() != fileTree(compileWithoutOptimization.destinationDir).singleFile.size()
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableIntegerOptimization/src/main/groovy/IntegerCalculations.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableIntegerOptimization/src/main/groovy/IntegerCalculations.groovy
new file mode 100644
index 0000000..97747ee
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canEnableAndDisableIntegerOptimization/src/main/groovy/IntegerCalculations.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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.
+ */
+
+class IntegerCalculations {
+    def calculate() {
+        int a = 9834
+        int b = 238923
+        int c = a + b
+        int d = c - a
+        int e = d * c
+        int f = e.intdiv(5)
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/build.gradle
new file mode 100644
index 0000000..7520244
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile "junit:junit:4.10"
+}
+
+sourceSets.test.groovy.filter.includes = ["**/*.spec"]
+
+compileTestGroovy {
+    groovyOptions.fileExtensions = ["spec"]
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person.spec b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person.spec
new file mode 100644
index 0000000..f104d7c
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person.spec
@@ -0,0 +1,4 @@
+class Person {
+    @org.junit.Test
+    void testMe() {}
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person2.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person2.groovy
new file mode 100644
index 0000000..ed11e7e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec/canUseCustomFileExtensions/src/test/groovy/Person2.groovy
@@ -0,0 +1,5 @@
+class Person2 {
+    @org.junit.Test
+    void testMe() {}
+}
+
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/build.gradle
new file mode 100644
index 0000000..664d7f7
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+compileGroovy.groovyOptions.listFiles = true
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/src/main/groovy/compile/test/Person.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/src/main/groovy/compile/test/Person.groovy
new file mode 100644
index 0000000..c65b3ee
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/src/main/groovy/compile/test/Person.groovy
@@ -0,0 +1,3 @@
+package compile.test
+
+class Person {}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/src/main/groovy/compile/test/Person2.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/src/main/groovy/compile/test/Person2.groovy
new file mode 100644
index 0000000..4a2e547
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec/canListSourceFiles/src/main/groovy/compile/test/Person2.groovy
@@ -0,0 +1,3 @@
+package compile.test
+
+class Person2 {}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/build.gradle
new file mode 100644
index 0000000..669c8e9
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy localGroovy()
+    testCompile "junit:junit:4.10"
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/main/groovy/TestCase.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/main/groovy/TestCase.java
new file mode 100644
index 0000000..69e4861
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/main/groovy/TestCase.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012 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.
+ */
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+ at GroovyASTTransformationClass({"TestCaseTransform"})
+public @interface TestCase {}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/main/groovy/TestCaseTransform.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/main/groovy/TestCaseTransform.java
new file mode 100644
index 0000000..f058d72
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/main/groovy/TestCaseTransform.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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.
+ */
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.ASTTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+
+import groovy.util.GroovyTestCase;
+
+ at GroovyASTTransformation
+public class TestCaseTransform implements ASTTransformation {
+    public void visit(ASTNode[] nodes, SourceUnit source) {
+        ClassNode clazz = (ClassNode) nodes[1];
+        clazz.setSuperClass(new ClassNode(GroovyTestCase.class));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/test/groovy/TestCaseTransformTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/test/groovy/TestCaseTransformTest.groovy
new file mode 100644
index 0000000..f9ced50
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseAstTransformThatReferencesGroovyTestCase/src/test/groovy/TestCaseTransformTest.groovy
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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.
+ */
+
+import org.junit.Test
+
+class TestCaseTransformTest {
+    @Test
+    void addsGroovyTestCaseAsSuperclass() {
+        assert MyTestCase.superclass == GroovyTestCase
+    }
+
+    @TestCase
+    static abstract class MyTestCase {}
+}
+
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/TestDelegate.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/TestDelegate.groovy
index 286a5c1..7005342 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/TestDelegate.groovy
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/TestDelegate.groovy
@@ -1,3 +1,4 @@
+package org.gradle.groovy.compile.GroovyCompilerIntegrationSpec.canUseBuiltInAstTransform.src.test.groovy
 
 class TestDelegate {
     def doStuff(String value) {
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/UseBuiltInTransformTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/UseBuiltInTransformTest.groovy
index 612394d..4be5887 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/UseBuiltInTransformTest.groovy
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseBuiltInAstTransform/src/test/groovy/UseBuiltInTransformTest.groovy
@@ -1,5 +1,6 @@
+package org.gradle.groovy.compile.GroovyCompilerIntegrationSpec.canUseBuiltInAstTransform.src.test.groovy
+
 import org.junit.Test
-import TestDelegate
 
 class UseBuiltInTransformTest {
     @Delegate final TestDelegate delegate = new TestDelegate()
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterfaceTransform.java b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterfaceTransform.java
index 10aca0e..1b1370f 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterfaceTransform.java
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/GroovyCompilerIntegrationSpec/canUseThirdPartyAstTransform/src/main/java/MagicInterfaceTransform.java
@@ -1,13 +1,9 @@
 import org.codehaus.groovy.ast.ASTNode;
-import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
 import org.codehaus.groovy.control.SourceUnit;
 import org.codehaus.groovy.transform.ASTTransformation;
 import org.codehaus.groovy.transform.GroovyASTTransformation;
 
-import java.lang.reflect.Modifier;
-
 @GroovyASTTransformation
 public class MagicInterfaceTransform implements ASTTransformation {
     public void visit(ASTNode[] nodes, SourceUnit source) {
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec/canEnableAndDisableInvokeDynamicOptimization/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec/canEnableAndDisableInvokeDynamicOptimization/build.gradle
new file mode 100644
index 0000000..5efd26f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec/canEnableAndDisableInvokeDynamicOptimization/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+task compileWithOptimization(type: GroovyCompile) {
+    source = sourceSets.main.groovy
+    classpath = configurations.compile
+    destinationDir = file("$sourceSets.main.output.classesDir/optimized")
+    groovyOptions.optimizationOptions.indy = true
+}
+
+task compileWithoutOptimization(type: GroovyCompile) {
+    source = sourceSets.main.groovy
+    classpath = configurations.compile
+    destinationDir = file("$sourceSets.main.output.classesDir/unoptimized")
+    groovyOptions.optimizationOptions.indy = false
+}
+
+task sanityCheck(dependsOn: [compileWithOptimization, compileWithoutOptimization]) << {
+    assert file("$compileWithOptimization.destinationDir/MethodInvocations.class").size() != file("$compileWithoutOptimization.destinationDir/MethodInvocations.class").size()
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec/canEnableAndDisableInvokeDynamicOptimization/src/main/groovy/MethodInvocations.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec/canEnableAndDisableInvokeDynamicOptimization/src/main/groovy/MethodInvocations.groovy
new file mode 100644
index 0000000..689ad89
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/groovy/compile/InvokeDynamicGroovyCompilerSpec/canEnableAndDisableInvokeDynamicOptimization/src/main/groovy/MethodInvocations.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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.
+ */
+
+class MethodInvocations {
+    void invoke() {
+        foo()
+    }
+
+    void foo() {
+        new Bar().bar()
+    }
+
+    static class Bar {
+        void bar() {
+            Baz.baz()
+        }
+    }
+
+    static class Baz {
+        static void baz() {}
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
index 8dfadc2..f86dbe1 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
@@ -68,6 +68,7 @@ public class OkTest {
         }
 
         // check other environmental stuff
+        assertEquals("Test worker", Thread.currentThread().getName());
         assertNull(System.getSecurityManager());
 
         // check stdout and stderr and logging
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle
new file mode 100644
index 0000000..937c489
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy "org.codehaus.groovy:groovy-all:1.8.6"
+    testCompile "junit:junit:4.10"
+}
+
+test {
+    testLogging {
+        quiet {
+            events "skipped", "failed"
+            minGranularity 2
+            maxGranularity -1
+            displayGranularity 3
+            exceptionFormat "full"
+            stackTraceFilters "truncate", "groovy"
+        }
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/src/test/groovy/org/gradle/JUnit4Test.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/src/test/groovy/org/gradle/JUnit4Test.groovy
new file mode 100644
index 0000000..e34e121
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/shared/src/test/groovy/org/gradle/JUnit4Test.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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 org.gradle
+
+import org.junit.Ignore
+import org.junit.Test
+
+class JUnit4Test {
+    @Test
+    void goodTest() {}
+
+    @Test
+    void badTest() {
+        beBad()
+    }
+
+    @Test
+    void printTest() {
+        println "line 1\nline 2"
+        println "line 3"
+    }
+
+    @Test
+    @Ignore
+    void ignoredTest() {
+        throw new RuntimeException("ignored")
+    }
+
+    private beBad() {
+        throw new RuntimeException("bad")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle
new file mode 100644
index 0000000..a3767ea
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/build.gradle
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy "org.codehaus.groovy:groovy-all:1.8.6"
+    testCompile "junit:junit:4.10"
+}
+
+test {
+    testLogging {
+        quiet {
+            events "standardOut", "standardError"
+        }
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/JUnit4StandardOutputTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/JUnit4StandardOutputTest.groovy
new file mode 100644
index 0000000..0cb930e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/JUnit4StandardOutputTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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 org.gradle
+
+import org.junit.Ignore
+import org.junit.Test
+
+class JUnit4StandardOutputTest {
+    @Test
+    void goodTest() {}
+
+    @Test
+    void badTest() {
+        beBad()
+    }
+
+    @Test
+    void printTest() {
+        println "line 1\nline 2"
+        println "line 3"
+    }
+
+    @Test
+    @Ignore
+    void ignoredTest() {
+        throw new RuntimeException("ignored")
+    }
+
+    private beBad() {
+        throw new RuntimeException("bad")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
index 87566c9..ac7a3ad 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
@@ -27,6 +27,10 @@ public class OkTest {
         // check env vars
         assertEquals("value", System.getenv("TEST_ENV_VAR"));
 
+        // check other environmental stuff
+        assertEquals("Test worker", Thread.currentThread().getName());
+        assertNull(System.getSecurityManager());
+
         // check logging
         System.out.println("stdout");
         System.err.println("stderr");
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
new file mode 100644
index 0000000..05fd14b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: "groovy"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy "org.codehaus.groovy:groovy-all:1.8.6"
+    testCompile "org.testng:testng:6.3.1"
+}
+
+test {
+    useTestNG()
+    testLogging {
+        quiet {
+            events "skipped", "failed"
+            minGranularity 2
+            maxGranularity -1
+            displayGranularity 3
+            exceptionFormat "full"
+            stackTraceFilters "truncate", "groovy"
+        }
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/src/test/groovy/org/gradle/TestNGTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/src/test/groovy/org/gradle/TestNGTest.groovy
new file mode 100644
index 0000000..6fa79fe
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/shared/src/test/groovy/org/gradle/TestNGTest.groovy
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 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 org.gradle
+
+import org.testng.annotations.Test
+
+class TestNGTest {
+    @Test
+    void goodTest() {}
+
+    @Test(dependsOnMethods = ["goodTest"])
+    void badTest() {
+        beBad()
+    }
+
+    @Test(dependsOnMethods = ["badTest"])
+    void ignoredTest() {}
+
+    @Test(dependsOnMethods = ["goodTest"])
+    void printTest() {
+        println "line 1\nline 2"
+        println "line 3"
+    }
+
+    private beBad() {
+        throw new RuntimeException("bad")
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
new file mode 100644
index 0000000..95d4d5f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/build.gradle
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy "org.codehaus.groovy:groovy-all:1.8.6"
+    testCompile "org.testng:testng:6.3.1"
+}
+
+test {
+    useTestNG()
+    testLogging {
+        quiet {
+            events "standardOut", "standardError"
+        }
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/TestNGStandardOutputTest.groovy b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/TestNGStandardOutputTest.groovy
new file mode 100644
index 0000000..becc609
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGLoggingIntegrationTest/standardOutputLogging/src/test/groovy/org/gradle/TestNGStandardOutputTest.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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 org.gradle
+
+import org.testng.annotations.Test
+
+class TestNGStandardOutputTest {
+    @Test
+    void printTest() {
+        println "line 1\nline 2"
+        println "line 3"
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/AbstractRule.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/AbstractRule.java
new file mode 100644
index 0000000..330b1c8
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/AbstractRule.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.plugins;
+
+import org.gradle.api.Rule;
+
+public abstract class AbstractRule implements Rule {
+
+    @Override
+    public String toString() {
+        return String.format("Rule: %s", getDescription());
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/BuildConfigurationRule.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/BuildConfigurationRule.java
new file mode 100644
index 0000000..6d2096c
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/BuildConfigurationRule.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.plugins;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.tasks.TaskContainer;
+
+public class BuildConfigurationRule extends AbstractRule {
+
+    public static final String PREFIX = "build";
+
+    private final ConfigurationContainer configurations;
+    private final TaskContainer tasks;
+
+    public BuildConfigurationRule(ConfigurationContainer configurations, TaskContainer tasks) {
+        this.configurations = configurations;
+        this.tasks = tasks;
+    }
+
+    public String getDescription() {
+        return String.format("Pattern: %s<ConfigurationName>: Assembles the artifacts of a configuration.", PREFIX);
+    }
+
+    public void apply(String taskName) {
+        if (taskName.startsWith(PREFIX)) {
+            String configurationName = StringUtils.uncapitalize(taskName.substring(PREFIX.length()));
+            Configuration configuration = configurations.findByName(configurationName);
+
+            if (configuration != null) {
+                Task task = tasks.add(taskName);
+                task.dependsOn(configuration.getAllArtifacts());
+                task.setDescription(String.format("Builds the artifacts belonging to %s.", configuration));
+            }
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/CleanRule.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/CleanRule.java
new file mode 100644
index 0000000..66fcc30
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/CleanRule.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.plugins;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Delete;
+import org.gradle.api.tasks.TaskContainer;
+
+public class CleanRule extends AbstractRule {
+
+    public static final String PREFIX = "clean";
+
+    private final TaskContainer tasks;
+
+    public CleanRule(TaskContainer tasks) {
+        this.tasks = tasks;
+    }
+
+    public String getDescription() {
+        return String.format("Pattern: %s<TaskName>: Cleans the output files of a task.", PREFIX);
+    }
+
+    public void apply(String taskName) {
+        if (!taskName.startsWith(PREFIX)) {
+            return;
+        }
+
+        String targetTaskName = taskName.substring(PREFIX.length());
+        if (Character.isLowerCase(targetTaskName.charAt(0))) {
+            return;
+        }
+
+        Task task = tasks.findByName(StringUtils.uncapitalize(targetTaskName));
+        if (task == null) {
+            return;
+        }
+
+        Delete clean = tasks.add(taskName, Delete.class);
+        clean.delete(task.getOutputs().getFiles());
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/UploadRule.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/UploadRule.java
new file mode 100644
index 0000000..676b24e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/plugins/UploadRule.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.plugins;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.plugins.BasePlugin;
+import org.gradle.api.tasks.Upload;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+public class UploadRule extends AbstractRule {
+
+    public static final String PREFIX = "upload";
+
+    private final Project project;
+
+    public UploadRule(Project project) {
+        this.project = project;
+    }
+
+    public String getDescription() {
+        return String.format("Pattern: %s<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration.", PREFIX);
+    }
+
+    public void apply(String taskName) {
+        for (Configuration configuration :  project.getConfigurations()) {
+            if (taskName.equals(configuration.getUploadTaskName())) {
+                createUploadTask(configuration.getUploadTaskName(), configuration, project);
+            }
+        }
+    }
+
+    private Upload createUploadTask(String name, final Configuration configuration, final Project project) {
+        Upload upload = project.getTasks().add(name, Upload.class);
+        upload.setDescription(String.format("Uploads all artifacts belonging to %s", configuration));
+        upload.setGroup(BasePlugin.UPLOAD_GROUP);
+        upload.setConfiguration(configuration);
+        upload.setUploadDescriptor(true);
+        upload.getConventionMapping().map("descriptorDestination", new Callable<File>() {
+            public File call() throws Exception {
+                return new File(project.getBuildDir(), "ivy.xml");
+            }
+        });
+        return upload;
+    }
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
index 3b73234..0c3b012 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
@@ -18,7 +18,7 @@ package org.gradle.api.internal.tasks;
 import groovy.lang.Closure;
 import org.gradle.api.Namer;
 import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.SourceSetContainer;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
index b6a9a97..e91b72b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ApiGroovyCompiler.java
@@ -25,7 +25,7 @@ import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit;
 import org.codehaus.groovy.tools.javac.JavaCompiler;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.WorkResult;
-import org.gradle.util.DefaultClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.util.FilteringClassLoader;
 
 import java.io.File;
@@ -49,6 +49,10 @@ public class ApiGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec>,
         configuration.setSourceEncoding(spec.getGroovyCompileOptions().getEncoding());
         configuration.setTargetBytecode(spec.getTargetCompatibility());
         configuration.setTargetDirectory(spec.getDestinationDir());
+        canonicalizeValues(spec.getGroovyCompileOptions().getOptimizationOptions());
+        try {
+            configuration.setOptimizationOptions(spec.getGroovyCompileOptions().getOptimizationOptions());
+        } catch (NoSuchMethodError ignored) { /* method was only introduced in Groovy 1.8 */ }
         Map<String, Object> jointCompilationOptions = new HashMap<String, Object>();
         jointCompilationOptions.put("stubDir", spec.getGroovyCompileOptions().getStubDir());
         jointCompilationOptions.put("keepStubs", spec.getGroovyCompileOptions().isKeepStubs());
@@ -67,6 +71,11 @@ public class ApiGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec>,
         FilteringClassLoader groovyCompilerClassLoader = new FilteringClassLoader(GroovyClassLoader.class.getClassLoader());
         groovyCompilerClassLoader.allowPackage("org.codehaus.groovy");
         groovyCompilerClassLoader.allowPackage("groovy");
+        // Disallow classes from Groovy Jar that reference external classes. Such classes must be loaded from astTransformClassLoader,
+        // or a NoClassDefFoundError will occur. Essentially this is drawing a line between the Groovy compiler and the Groovy
+        // library, albeit only for selected classes that run a high risk of being statically referenced from a transform.
+        groovyCompilerClassLoader.disallowClass("groovy.util.GroovyTestCase");
+        groovyCompilerClassLoader.disallowClass("groovy.servlet.GroovyServlet");
 
         // AST transforms need their own class loader that shares compiler classes with the compiler itself
         final GroovyClassLoader astTransformClassLoader = new GroovyClassLoader(groovyCompilerClassLoader, null);
@@ -115,4 +124,16 @@ public class ApiGroovyCompiler implements Compiler<GroovyJavaJointCompileSpec>,
         return new SimpleWorkResult(true);
     }
 
+    // Make sure that map only contains Boolean.TRUE and Boolean.FALSE values and no other Boolean instances.
+    // This is necessary because:
+    // 1. serialization/deserialization of the compile spec doesn't preserve Boolean.TRUE/Boolean.FALSE but creates new instances
+    // 1. org.codehaus.groovy.classgen.asm.WriterController makes identity comparisons
+    private void canonicalizeValues(Map<String, Boolean> options) {
+        for (String key : options.keySet()) {
+            // unboxing and boxing does the trick
+            boolean value = options.get(key);
+            options.put(key, value);
+        }
+    }
+
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgCollector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgCollector.java
new file mode 100644
index 0000000..47e0c3a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgCollector.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.compile;
+
+public interface ArgCollector {
+    
+    ArgCollector args(Object... args);
+
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgWriter.java
new file mode 100755
index 0000000..6fb66a0
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ArgWriter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.compile;
+
+import org.gradle.api.Transformer;
+
+import java.io.PrintWriter;
+import java.util.regex.Pattern;
+
+public class ArgWriter implements ArgCollector {
+    private static final Pattern WHITESPACE = Pattern.compile("\\s");
+    private final PrintWriter writer;
+    private final boolean backslashEscape;
+
+    private ArgWriter(PrintWriter writer, boolean backslashEscape) {
+        this.writer = writer;
+        this.backslashEscape = backslashEscape;
+    }
+
+    public static ArgWriter unixStyle(PrintWriter writer) {
+        return new ArgWriter(writer, true);
+    }
+
+    public static Transformer<ArgWriter, PrintWriter> unixStyleFactory() {
+        return new Transformer<ArgWriter, PrintWriter>() {
+            public ArgWriter transform(PrintWriter original) {
+                return unixStyle(original);
+            }
+        };
+    }
+
+    public static ArgWriter windowsStyle(PrintWriter writer) {
+        return new ArgWriter(writer, false);
+    }
+
+    public static Transformer<ArgWriter, PrintWriter> windowsStyleFactory() {
+        return new Transformer<ArgWriter, PrintWriter>() {
+            public ArgWriter transform(PrintWriter original) {
+                return windowsStyle(original);
+            }
+        };
+    }
+
+    /**
+     * Writes a set of args on a single line, escaping and quoting as required.
+     */
+    public ArgWriter args(Object... args) {
+        for (int i = 0; i < args.length; i++) {
+            Object arg = args[i];
+            if (i > 0) {
+                writer.print(' ');
+            }
+            String str = arg.toString();
+            if (backslashEscape) {
+                str = str.replace("\\", "\\\\").replace("\"", "\\\"");
+            }
+            if (WHITESPACE.matcher(str).find()) {
+                writer.print('\"');
+                writer.print(str);
+                writer.print('\"');
+            } else {
+                writer.print(str);
+            }
+        }
+        writer.println();
+        return this;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java
index ea85ee7..5ee946e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompiler.java
@@ -32,7 +32,7 @@ import java.io.File;
 public class CommandLineJavaCompiler implements Compiler<JavaCompileSpec> {
     private static final Logger LOGGER = LoggerFactory.getLogger(CommandLineJavaCompiler.class);
 
-    private final CommandLineJavaCompilerArgumentsGenerator argumentsGenerator;
+    private final CompileSpecToArguments<JavaCompileSpec> argumentsGenerator;
     private final File workingDir;
 
     public CommandLineJavaCompiler(TemporaryFileProvider tempFileProvider, File workingDir) {
@@ -44,18 +44,17 @@ public class CommandLineJavaCompiler implements Compiler<JavaCompileSpec> {
         String executable = spec.getCompileOptions().getForkOptions().getExecutable();
         LOGGER.info("Compiling with Java command line compiler '{}'.", executable);
 
-        Iterable<String> args = argumentsGenerator.generate(spec);
-        ExecHandle handle = createCompilerHandle(executable, args);
+        ExecHandle handle = createCompilerHandle(executable, spec);
         executeCompiler(handle);
 
         return new SimpleWorkResult(true);
     }
 
-    private ExecHandle createCompilerHandle(String executable, Iterable<String> args) {
+    private ExecHandle createCompilerHandle(String executable, JavaCompileSpec spec) {
         ExecHandleBuilder builder = new ExecHandleBuilder();
         builder.setWorkingDir(workingDir);
         builder.setExecutable(executable);
-        builder.setArgs(args);
+        argumentsGenerator.collectArguments(spec, new ExecSpecBackedArgCollector(builder));
         builder.setIgnoreExitValue(true);
         return builder.build();
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGenerator.java
index c0963df..cd9b9e0 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGenerator.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGenerator.java
@@ -17,22 +17,29 @@
 package org.gradle.api.internal.tasks.compile;
 
 import com.google.common.collect.Iterables;
-
+import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.file.TemporaryFileProvider;
-import org.gradle.util.GFileUtils;
 
 import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.Collections;
 import java.util.List;
-import java.util.ListIterator;
 
-public class CommandLineJavaCompilerArgumentsGenerator {
+public class CommandLineJavaCompilerArgumentsGenerator implements CompileSpecToArguments<JavaCompileSpec> {
     private final TemporaryFileProvider tempFileProvider;
 
     public CommandLineJavaCompilerArgumentsGenerator(TemporaryFileProvider tempFileProvider) {
         this.tempFileProvider = tempFileProvider;
     }
 
+    public void collectArguments(JavaCompileSpec spec, ArgCollector collector) {
+        for (String arg : generate(spec)) {
+            collector.args(arg);
+        }
+    }
+
     public Iterable<String> generate(JavaCompileSpec spec) {
         JavaCompilerArgumentsBuilder builder = new JavaCompilerArgumentsBuilder(spec);
         List<String> launcherOptions = builder.includeLauncherOptions(true).includeMainOptions(false).includeSourceFiles(false).build();
@@ -58,19 +65,21 @@ public class CommandLineJavaCompilerArgumentsGenerator {
 
     private Iterable<String> shortenArgs(List<String> args) {
         File file = tempFileProvider.createTemporaryFile("compile-args", null, "java-compiler");
+        // for command file format, see http://docs.oracle.com/javase/6/docs/technotes/tools/windows/javac.html#commandlineargfile
         // use platform character and line encoding
-        GFileUtils.writeLines(file, quoteArgs(args));
-        return Collections.singleton("@" + file.getPath());
-    }
-
-    private List<String> quoteArgs(List<String> args) {
-        ListIterator<String> iterator = args.listIterator();
-        while (iterator.hasNext()) {
-            String arg = iterator.next();
-            // assumption: blindly quoting all args is fine
-            // as for how to quote, see http://docs.oracle.com/javase/6/docs/technotes/tools/windows/javac.html#commandlineargfile
-            iterator.set("\"" + arg.replace("\\", "\\\\") + "\"");
+        try {
+            PrintWriter writer = new PrintWriter(new FileWriter(file));
+            try {
+                ArgWriter argWriter = ArgWriter.unixStyle(writer);
+                for (String arg : args) {
+                    argWriter.args(arg);
+                }
+            } finally {
+                writer.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
         }
-        return args;
+        return Collections.singleton("@" + file.getPath());
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompileSpecToArguments.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompileSpecToArguments.java
new file mode 100644
index 0000000..1afd750
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/CompileSpecToArguments.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.compile;
+
+public interface CompileSpecToArguments<T> {
+    public void collectArguments(T spec, ArgCollector collector);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ExecSpecBackedArgCollector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ExecSpecBackedArgCollector.java
new file mode 100644
index 0000000..c54832e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/ExecSpecBackedArgCollector.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.compile;
+
+import org.gradle.process.ExecSpec;
+
+public class ExecSpecBackedArgCollector implements ArgCollector {
+    private final ExecSpec action;
+
+    public ExecSpecBackedArgCollector(ExecSpec action) {
+        this.action = action;
+    }
+
+    public ArgCollector args(Object... args) {
+        action.args(args);
+        return this;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java
index 89e5197..556e890 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/InProcessJavaCompilerFactory.java
@@ -16,15 +16,15 @@
 package org.gradle.api.internal.tasks.compile;
 
 import org.gradle.api.GradleException;
+import org.gradle.api.JavaVersion;
 import org.gradle.api.tasks.compile.CompileOptions;
-import org.gradle.internal.jvm.Jvm;
 import org.gradle.util.ReflectionUtil;
 
 public class InProcessJavaCompilerFactory implements JavaCompilerFactory {
     private static final boolean SUN_COMPILER_AVAILABLE = ReflectionUtil.isClassAvailable("com.sun.tools.javac.Main");
 
     public Compiler<JavaCompileSpec> create(CompileOptions options) {
-        if (Jvm.current().isJava6Compatible()) {
+        if (JavaVersion.current().isJava6Compatible()) {
             return createJdk6Compiler();
         }
         if (SUN_COMPILER_AVAILABLE) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java
index 37518a2..57e5d6b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompiler.java
@@ -48,14 +48,19 @@ public class NormalizingGroovyCompiler implements Compiler<GroovyJavaJointCompil
         return delegateAndHandleErrors(spec);
     }
 
-    private void resolveAndFilterSourceFiles(GroovyJavaJointCompileSpec spec) {
-        FileCollection groovyJavaOnly = spec.getSource().filter(new Spec<File>() {
+    private void resolveAndFilterSourceFiles(final GroovyJavaJointCompileSpec spec) {
+        FileCollection filtered = spec.getSource().filter(new Spec<File>() {
             public boolean isSatisfiedBy(File element) {
-                return element.getName().endsWith(".groovy") || element.getName().endsWith(".java");
+                for (String fileExtension : spec.getGroovyCompileOptions().getFileExtensions()) {
+                    if (element.getName().endsWith("." + fileExtension)) {
+                        return true;
+                    }
+                }
+                return false;
             }
         });
 
-        spec.setSource(new SimpleFileCollection(groovyJavaOnly.getFiles()));
+        spec.setSource(new SimpleFileCollection(filtered.getFiles()));
     }
 
     private void resolveClasspath(GroovyJavaJointCompileSpec spec) {
@@ -69,7 +74,7 @@ public class NormalizingGroovyCompiler implements Compiler<GroovyJavaJointCompil
     }
 
     private void logSourceFiles(GroovyJavaJointCompileSpec spec) {
-        if (!spec.getCompileOptions().isListFiles()) { return; }
+        if (!spec.getGroovyCompileOptions().isListFiles()) { return; }
 
         StringBuilder builder = new StringBuilder();
         builder.append("Source files to be compiled:");
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoader.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoader.java
index fd442ad..d5fc2c2 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoader.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoader.java
@@ -19,7 +19,7 @@ package org.gradle.api.internal.tasks.compile;
 import com.google.common.io.ByteStreams;
 import org.codehaus.groovy.transform.GroovyASTTransformationClass;
 import org.gradle.api.UncheckedIOException;
-import org.gradle.util.ClassPath;
+import org.gradle.internal.classpath.ClassPath;
 import org.gradle.util.MutableURLClassLoader;
 import org.objectweb.asm.*;
 import org.objectweb.asm.commons.EmptyVisitor;
@@ -31,8 +31,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 /**
- * Transforms @GroovyASTTransformationClass(classes = {classLiterals}) into @GroovyASTTransformationClass([classNames]), to work around Groovy's
- * loading of transformer classes.
+ * Transforms @GroovyASTTransformationClass(classes = {classLiterals}) into @GroovyASTTransformationClass([classNames]),
+ * to work around GROOVY-5416.
  */
 class TransformingClassLoader extends MutableURLClassLoader {
     private static final String ANNOTATION_DESCRIPTOR = Type.getType(GroovyASTTransformationClass.class).getDescriptor();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java
index 8ced16c..0378c04 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/InProcessCompilerDaemonFactory.java
@@ -20,6 +20,8 @@ import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.compile.CompileSpec;
 import org.gradle.api.internal.tasks.compile.Compiler;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.classpath.DefaultClassPath;
+import org.gradle.internal.io.ClassLoaderObjectInputStream;
 import org.gradle.util.*;
 
 import java.io.ByteArrayInputStream;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java
index 649ea40..42ec863 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.tasks.testing;
 
 import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 public class SuiteTestClassProcessor implements TestClassProcessor {
     private final TestClassProcessor processor;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
index f721cda..ff93258 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
@@ -140,7 +140,7 @@ public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor>
 
     /**
      * In none super class mode a test class is published when the class is a test and it is not abstract. In super
-     * class mode it musn't publish the class otherwise it will get published multiple times (for each extending
+     * class mode it must not publish the class otherwise it will get published multiple times (for each extending
      * class).
      */
     protected void publishTestClass(boolean isTest, TestClassVisitor classVisitor, boolean superClass) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
index 75ddcb1..0808634 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
@@ -17,6 +17,9 @@ package org.gradle.api.internal.tasks.testing.detection;
 
 import org.apache.commons.lang.text.StrBuilder;
 import org.gradle.api.GradleException;
+import org.gradle.api.internal.file.DefaultTemporaryFileProvider;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.internal.Factory;
 import org.gradle.util.JarUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,10 +38,10 @@ public class ClassFileExtractionManager {
     private final Map<String, Set<File>> packageJarFilesMappings;
     private final Map<String, File> extractedJarClasses;
     private final Set<String> unextractableClasses;
-    private final File tempDir;
+    private final TemporaryFileProvider tempDirProvider;
 
-    public ClassFileExtractionManager(File tempDir) {
-        this.tempDir = tempDir;
+    public ClassFileExtractionManager(final Factory<File> tempDirFactory) {
+        tempDirProvider = new DefaultTemporaryFileProvider(tempDirFactory);
         packageJarFilesMappings = new HashMap<String, Set<File>>();
         extractedJarClasses = new HashMap<String, File>();
         unextractableClasses = new TreeSet<String>();
@@ -85,7 +88,7 @@ public class ClassFileExtractionManager {
         }
     }
 
-    boolean extractClassFile(final String className) {
+    private boolean extractClassFile(final String className) {
         boolean classFileExtracted = false;
 
         final File extractedClassFile = tempFile();
@@ -122,7 +125,7 @@ public class ClassFileExtractionManager {
         return classFileExtracted;
     }
 
-    String classNamePackage(final String className) {
+    private String classNamePackage(final String className) {
         final int lastSlashIndex = className.lastIndexOf('/');
 
         if (lastSlashIndex == -1) {
@@ -132,13 +135,7 @@ public class ClassFileExtractionManager {
         }
     }
 
-    File tempFile() {
-        try {
-            final File tempFile = File.createTempFile("jar_extract_", "_tmp", tempDir);
-            tempFile.deleteOnExit();
-            return tempFile;
-        } catch (IOException e) {
-            throw new GradleException("failed to create temp file to extract class from jar into", e);
-        }
+    private File tempFile() {
+        return tempDirProvider.createTemporaryFile("jar_extract_", "_tmp"); // Could throw UncheckedIOException
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java
index e480028..88b5254 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java
@@ -26,8 +26,8 @@ import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import java.io.File;
 
 /**
- * The default test class scanner depending on the availability of a test framework detecter a detection or filename
- * scan is performed to find test classes.
+ * The default test class scanner. Depending on the availability of a test framework detector,
+ * a detection or filename scan is performed to find test classes.
  *
  * @author Tom Eyckmans
  */
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java
index 4bc9b10..f54a0cb 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java
@@ -27,9 +27,9 @@ import org.gradle.api.internal.tasks.testing.processors.RestartEveryNTestClassPr
 import org.gradle.api.internal.tasks.testing.processors.TestMainAction;
 import org.gradle.api.internal.tasks.testing.worker.ForkingTestClassProcessor;
 import org.gradle.api.tasks.testing.Test;
+import org.gradle.internal.TrueTimeProvider;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
-import org.gradle.util.TrueTimeProvider;
 
 /**
  * The default test class scanner factory.
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
index e42f672..c8c2d83 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
@@ -21,13 +21,13 @@ import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.internal.tasks.testing.processors.CaptureTestOutputTestResultProcessor;
 import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor;
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.TrueTimeProvider;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.logging.StandardOutputRedirector;
 import org.gradle.messaging.actor.Actor;
 import org.gradle.messaging.actor.ActorFactory;
-import org.gradle.util.IdGenerator;
-import org.gradle.util.TimeProvider;
-import org.gradle.util.TrueTimeProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
index 32f013e..6f639b4 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
@@ -18,9 +18,9 @@ package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.internal.tasks.testing.*;
 import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.internal.TimeProvider;
 import org.gradle.internal.concurrent.ThreadSafe;
-import org.gradle.util.IdGenerator;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.id.IdGenerator;
 import org.junit.runner.Description;
 import org.junit.runner.notification.Failure;
 import org.junit.runner.notification.RunListener;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
index f8cc67b..e4632c1 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
@@ -17,6 +17,7 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.Action;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestFramework;
@@ -28,7 +29,6 @@ import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.junit.JUnitOptions;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.process.internal.WorkerProcessBuilder;
-import org.gradle.util.IdGenerator;
 
 import java.io.File;
 import java.io.Serializable;
@@ -46,7 +46,7 @@ public class JUnitTestFramework implements TestFramework {
         this.testTask = testTask;
         reporter = new DefaultTestReport();
         options = new JUnitOptions();
-        detector = new JUnitDetector(testTask.getTestClassesDir(), testTask.getClasspath(), new ClassFileExtractionManager(testTask.getTemporaryDir()));
+        detector = new JUnitDetector(testTask.getTestClassesDir(), testTask.getClasspath(), new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
     }
 
     public WorkerTestClassProcessorFactory getProcessorFactory() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGenerator.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGenerator.java
index 010a0dc..7a1ef84 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGenerator.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGenerator.java
@@ -18,8 +18,8 @@ package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.internal.tasks.testing.*;
 import org.gradle.api.tasks.testing.TestOutputEvent;
-import org.gradle.util.IdGenerator;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
+import org.gradle.internal.id.IdGenerator;
 
 import java.util.LinkedHashSet;
 import java.util.Set;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLogger.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLogger.java
new file mode 100644
index 0000000..6bb32d1
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLogger.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import org.gradle.api.Nullable;
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.logging.TestLogEvent;
+import org.gradle.logging.StyledTextOutput;
+import org.gradle.logging.StyledTextOutputFactory;
+import org.gradle.util.TextUtil;
+
+import java.util.List;
+
+/**
+ * Provides ability to log test events. Not thread safe.
+ */
+public abstract class AbstractTestLogger {
+    private final StyledTextOutputFactory textOutputFactory;
+    private final LogLevel logLevel;
+    private final int displayGranularity;
+    private Object lastSeenTestId;
+    private TestLogEvent lastSeenTestEvent;
+
+    protected AbstractTestLogger(StyledTextOutputFactory textOutputFactory, LogLevel logLevel, int displayGranularity) {
+        this.textOutputFactory = textOutputFactory;
+        this.logLevel = logLevel;
+        this.displayGranularity = displayGranularity;
+    }
+
+    protected void logEvent(TestDescriptor descriptor, TestLogEvent event) {
+        logEvent(descriptor, event, null);
+    }
+
+    protected void logEvent(TestDescriptor descriptor, TestLogEvent event, @Nullable String details) {
+        StyledTextOutput output = textOutputFactory.create("TestEventLogger", logLevel);
+        Object testId = ((TestDescriptorInternal) descriptor).getId();
+        if (!testId.equals(lastSeenTestId) || event != lastSeenTestEvent) {
+            output.append(TextUtil.getPlatformLineSeparator() + getEventPath(descriptor));
+            output.withStyle(getStyle(event)).println(event.toString());
+        }
+        lastSeenTestId = testId;
+        lastSeenTestEvent = event;
+        if (details != null) {
+            output.append(TextUtil.toPlatformLineSeparators(details));
+        }
+    }
+
+    private String getEventPath(TestDescriptor descriptor) {
+        List<String> names = Lists.newArrayList();
+        TestDescriptor current = descriptor;
+        while (current != null) {
+            if (isAtomicTestWhoseParentIsNotTheTestClass(current)) {
+                // This deals with the fact that in TestNG, there are no class-level events,
+                // but we nevertheless want to see the class name. We use "." rather than
+                // " > " as a separator to make it clear that the class is not a separate
+                // level. This matters when configuring min/max/displayGranularity.
+                names.add(current.getClassName() + "." + current.getName());
+            } else {
+                names.add(current.getName());
+            }
+            current = current.getParent();
+        }
+
+        int effectiveDisplayGranularity = displayGranularity == -1
+                ? names.size() - 1 : Math.min(displayGranularity, names.size() - 1);
+        List<String> displayedNames = Lists.reverse(names).subList(effectiveDisplayGranularity, names.size());
+        return Joiner.on(" > ").join(displayedNames) + " ";
+    }
+
+    private boolean isAtomicTestWhoseParentIsNotTheTestClass(TestDescriptor current) {
+        return !current.isComposite() && current.getClassName() != null && (current.getParent() == null
+                || !current.getClassName().equals(current.getParent().getName()));
+    }
+
+    private StyledTextOutput.Style getStyle(TestLogEvent event) {
+        switch (event) {
+            case PASSED: return StyledTextOutput.Style.Identifier;
+            case FAILED: return StyledTextOutput.Style.Failure;
+            case SKIPPED: return StyledTextOutput.Style.Info;
+            default: return StyledTextOutput.Style.Normal;
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/ClassMethodNameStackTraceSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/ClassMethodNameStackTraceSpec.java
new file mode 100644
index 0000000..f447c81
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/ClassMethodNameStackTraceSpec.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import org.gradle.api.Nullable;
+import org.gradle.api.specs.Spec;
+
+public class ClassMethodNameStackTraceSpec implements Spec<StackTraceElement> {
+    private final String className;
+    private final String methodName;
+
+    ClassMethodNameStackTraceSpec(@Nullable String className, @Nullable String methodName) {
+        this.className = className;
+        this.methodName = methodName;
+    }
+
+    public boolean isSatisfiedBy(StackTraceElement element) {
+        return (className == null || className.equals(element.getClassName()))
+                && (methodName == null || methodName.equals(element.getMethodName()));
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLogging.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLogging.java
index 8e9f5f3..4cfe4fc 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLogging.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLogging.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2012 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.
@@ -16,21 +16,133 @@
 
 package org.gradle.api.internal.tasks.testing.logging;
 
-import org.gradle.api.tasks.testing.TestLogging;
+import org.gradle.api.tasks.testing.logging.*;
+import org.gradle.util.GUtil;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.Set;
 
-/**
- * by Szczepan Faber, created at: 10/31/11
- */
 public class DefaultTestLogging implements TestLogging {
+    private Set<TestLogEvent> events = EnumSet.noneOf(TestLogEvent.class);
+    private int minGranularity = -1;
+    private int maxGranularity = -1;
+    private int displayGranularity = 2;
+    private boolean showExceptions = true;
+    private boolean showCauses = true;
+    private boolean showStackTraces = true;
+    private TestExceptionFormat exceptionFormat = TestExceptionFormat.FULL;
+    private Set<TestStackTraceFilter> stackTraceFilters = EnumSet.of(TestStackTraceFilter.TRUNCATE);
+
+    public Set<TestLogEvent> getEvents() {
+        return events;
+    }
+
+    public void setEvents(Iterable<?> events) {
+        this.events = toEnumSet(TestLogEvent.class, events);
+    }
+
+    public void events(Object... events) {
+        this.events.addAll(toEnumSet(TestLogEvent.class, events));
+    }
+
+    public int getMinGranularity() {
+        return minGranularity;
+    }
+
+    public void setMinGranularity(int granularity) {
+        minGranularity = granularity;
+    }
 
-    boolean showStandardStreams;
+    public int getMaxGranularity() {
+        return maxGranularity;
+    }
+
+    public void setMaxGranularity(int granularity) {
+        maxGranularity = granularity;
+    }
+
+    public int getDisplayGranularity() {
+        return displayGranularity;
+    }
+
+    public void setDisplayGranularity(int granularity) {
+        displayGranularity = granularity;
+    }
+
+    public boolean getShowExceptions() {
+        return showExceptions;
+    }
+
+    public void setShowExceptions(boolean flag) {
+        showExceptions = flag;
+    }
+
+    public boolean getShowCauses() {
+        return showCauses;
+    }
+
+    public void setShowCauses(boolean flag) {
+        showCauses = flag;
+    }
+
+    public boolean getShowStackTraces() {
+        return showStackTraces;
+    }
+
+    public void setShowStackTraces(boolean flag) {
+        showStackTraces = flag;
+    }
+
+    public TestExceptionFormat getExceptionFormat() {
+        return exceptionFormat;
+    }
+
+    public void setExceptionFormat(Object exceptionFormat) {
+        this.exceptionFormat = toEnum(TestExceptionFormat.class, exceptionFormat);
+    }
+
+    public Set<TestStackTraceFilter> getStackTraceFilters() {
+        return stackTraceFilters;
+    }
+
+    public void setStackTraceFilters(Iterable<?> filters) {
+        stackTraceFilters = toEnumSet(TestStackTraceFilter.class, filters);
+    }
+
+    public void stackTraceFilters(Object... filters) {
+        stackTraceFilters.addAll(toEnumSet(TestStackTraceFilter.class, filters));
+    }
 
     public boolean getShowStandardStreams() {
-        return showStandardStreams;
+        return events.contains(TestLogEvent.STANDARD_OUT) && events.contains(TestLogEvent.STANDARD_ERROR);
     }
 
-    public TestLogging setShowStandardStreams(boolean standardStreams) {
-        this.showStandardStreams = standardStreams;
+    public TestLogging setShowStandardStreams(boolean flag) {
+        events.addAll(EnumSet.of(TestLogEvent.STANDARD_OUT, TestLogEvent.STANDARD_ERROR));
         return this;
     }
-}
+
+    private <T extends Enum<T>> T toEnum(Class<T> enumType, Object value) {
+        if (enumType.isInstance(value)) {
+            return enumType.cast(value);
+        }
+        if (value instanceof CharSequence) {
+            return Enum.valueOf(enumType, GUtil.toConstant(value.toString()));
+        }
+        throw new IllegalArgumentException(String.format("Cannot convert value '%s' of type '%s' to enum type '%s'",
+                value, value.getClass(), enumType));
+    }
+
+    private <T extends Enum<T>> EnumSet<T> toEnumSet(Class<T> enumType, Object[] values) {
+        return toEnumSet(enumType, Arrays.asList(values));
+    }
+
+    private <T extends Enum<T>> EnumSet<T> toEnumSet(Class<T> enumType, Iterable<?> values) {
+        EnumSet<T> result = EnumSet.noneOf(enumType);
+        for (Object value : values) {
+            result.add(toEnum(enumType, value));
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingContainer.java
new file mode 100644
index 0000000..13e7cad
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingContainer.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import com.google.common.collect.Maps;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.tasks.testing.logging.*;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.Set;
+
+public class DefaultTestLoggingContainer implements TestLoggingContainer {
+    private final Map<LogLevel, TestLogging> perLevelTestLogging = Maps.newEnumMap(LogLevel.class);
+
+    public DefaultTestLoggingContainer(Instantiator instantiator) {
+        for (LogLevel level: LogLevel.values()) {
+            perLevelTestLogging.put(level, instantiator.newInstance(DefaultTestLogging.class));
+        }
+
+        setEvents(EnumSet.of(TestLogEvent.FAILED));
+        setExceptionFormat(TestExceptionFormat.SHORT);
+
+        getInfo().setEvents(EnumSet.of(TestLogEvent.FAILED, TestLogEvent.SKIPPED, TestLogEvent.STANDARD_OUT, TestLogEvent.STANDARD_ERROR));
+        getInfo().setStackTraceFilters(EnumSet.of(TestStackTraceFilter.TRUNCATE));
+
+        getDebug().setEvents(EnumSet.allOf(TestLogEvent.class));
+        getDebug().setMinGranularity(0);
+        getDebug().setStackTraceFilters(EnumSet.noneOf(TestStackTraceFilter.class));
+    }
+
+    public TestLogging getDebug() {
+        return perLevelTestLogging.get(LogLevel.DEBUG);
+    }
+
+    public void setDebug(TestLogging logging) {
+        perLevelTestLogging.put(LogLevel.DEBUG, logging);
+    }
+
+    public void debug(Action<TestLogging> action) {
+        action.execute(getDebug());
+    }
+
+    public TestLogging getInfo() {
+        return perLevelTestLogging.get(LogLevel.INFO);
+    }
+
+    public void setInfo(TestLogging logging) {
+        perLevelTestLogging.put(LogLevel.INFO, logging);
+    }
+
+    public void info(Action<TestLogging> action) {
+        action.execute(getInfo());
+    }
+
+    public TestLogging getLifecycle() {
+        return perLevelTestLogging.get(LogLevel.LIFECYCLE);
+    }
+
+    public void setLifecycle(TestLogging logging) {
+        perLevelTestLogging.put(LogLevel.LIFECYCLE, logging);
+    }
+
+    public void lifecycle(Action<TestLogging> action) {
+        action.execute(getLifecycle());
+    }
+
+    public TestLogging getWarn() {
+        return perLevelTestLogging.get(LogLevel.WARN);
+    }
+
+    public void setWarn(TestLogging logging) {
+        perLevelTestLogging.put(LogLevel.WARN, logging);
+    }
+
+    public void warn(Action<TestLogging> action) {
+        action.execute(getWarn());
+    }
+
+    public TestLogging getQuiet() {
+        return perLevelTestLogging.get(LogLevel.QUIET);
+    }
+
+    public void setQuiet(TestLogging logging) {
+        perLevelTestLogging.put(LogLevel.QUIET, logging);
+    }
+
+    public void quiet(Action<TestLogging> action) {
+        action.execute(getQuiet());
+    }
+
+    public TestLogging getError() {
+        return perLevelTestLogging.get(LogLevel.ERROR);
+    }
+
+    public void setError(TestLogging logging) {
+        perLevelTestLogging.put(LogLevel.ERROR, logging);
+    }
+
+    public void error(Action<TestLogging> action) {
+        action.execute(getError());
+    }
+
+    public Set<TestLogEvent> getEvents() {
+        return getLifecycle().getEvents();
+    }
+
+    public void setEvents(Iterable<?> events) {
+        getLifecycle().setEvents(events);
+    }
+
+    public void events(Object... events) {
+        getLifecycle().events(events);
+    }
+
+    public int getMinGranularity() {
+        return getLifecycle().getMinGranularity();
+    }
+
+    public void setMinGranularity(int granularity) {
+        getLifecycle().setMinGranularity(granularity);
+    }
+
+    public int getMaxGranularity() {
+        return getLifecycle().getMaxGranularity();
+    }
+
+    public void setMaxGranularity(int granularity) {
+        getLifecycle().setMaxGranularity(granularity);
+    }
+
+    public int getDisplayGranularity() {
+        return getLifecycle().getDisplayGranularity();
+    }
+
+    public void setDisplayGranularity(int granularity) {
+        getLifecycle().setDisplayGranularity(granularity);
+    }
+
+    public boolean getShowExceptions() {
+        return getLifecycle().getShowExceptions();
+    }
+
+    public void setShowExceptions(boolean flag) {
+        getLifecycle().setShowExceptions(flag);
+    }
+
+    public boolean getShowCauses() {
+        return getLifecycle().getShowCauses();
+    }
+
+    public void setShowCauses(boolean flag) {
+        getLifecycle().setShowCauses(flag);
+    }
+
+    public boolean getShowStackTraces() {
+        return getLifecycle().getShowStackTraces();
+    }
+
+    public void setShowStackTraces(boolean flag) {
+        getLifecycle().setShowStackTraces(flag);
+    }
+
+    public TestExceptionFormat getExceptionFormat() {
+        return getLifecycle().getExceptionFormat();
+    }
+
+    public void setExceptionFormat(Object exceptionFormat) {
+        getLifecycle().setExceptionFormat(exceptionFormat);
+    }
+
+    public Set<TestStackTraceFilter> getStackTraceFilters() {
+        return getLifecycle().getStackTraceFilters();
+    }
+
+    public void setStackTraceFilters(Iterable<?> stackTraces) {
+        getLifecycle().setStackTraceFilters(stackTraces);
+    }
+
+    public void stackTraceFilters(Object... stackTraces) {
+        getLifecycle().stackTraceFilters(stackTraces);
+    }
+
+    public boolean getShowStandardStreams() {
+        return getLifecycle().getShowStandardStreams();
+    }
+
+    public TestLoggingContainer setShowStandardStreams(boolean flag) {
+        getLifecycle().setShowStandardStreams(flag);
+        return this;
+    }
+
+    public TestLogging get(LogLevel level) {
+        return perLevelTestLogging.get(level);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatter.java
new file mode 100644
index 0000000..73a0781
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.gradle.api.Nullable;
+import org.gradle.api.specs.AndSpec;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.logging.TestLogging;
+import org.gradle.api.tasks.testing.logging.TestStackTraceFilter;
+import org.gradle.util.TextUtil;
+
+import java.util.List;
+
+public class FullExceptionFormatter implements TestExceptionFormatter {
+    private static final String INDENT = "    ";
+
+    private final TestLogging testLogging;
+
+    public FullExceptionFormatter(TestLogging testLogging) {
+        this.testLogging = testLogging;
+    }
+
+    public String format(TestDescriptor descriptor, List<Throwable> exceptions) {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < exceptions.size(); i++) {
+            printException(descriptor, exceptions.get(i), null, 0, builder);
+            if (i < exceptions.size() - 1) {
+                builder.append('\n');
+            }
+        }
+        return builder.toString();
+    }
+
+    private void printException(TestDescriptor descriptor, Throwable exception,
+                                @Nullable List<StackTraceElement> parentTrace, int exceptionLevel, StringBuilder builder) {
+        String exceptionIndent = Strings.repeat(INDENT, exceptionLevel + 1);
+        String exceptionText = exceptionLevel == 0 ? exception.toString() : "\nCaused by:\n" + exception.toString();
+        String indentedText = TextUtil.indent(exceptionText, exceptionIndent);
+        builder.append(indentedText);
+        builder.append('\n');
+
+        String stackTraceIndent = exceptionIndent + INDENT;
+        List<StackTraceElement> stackTrace = null;
+
+        if (testLogging.getShowStackTraces()) {
+            stackTrace = filterStackTrace(exception, descriptor);
+            int commonElements = countCommonElements(stackTrace, parentTrace);
+            for (int i = 0; i < stackTrace.size() - commonElements; i++) {
+                builder.append(stackTraceIndent);
+                builder.append("at ");
+                builder.append(stackTrace.get(i));
+                builder.append('\n');
+            }
+            if (commonElements != 0) {
+                builder.append(stackTraceIndent);
+                builder.append("... ");
+                builder.append(commonElements);
+                builder.append(" more");
+                builder.append('\n');
+            }
+        }
+
+        if (testLogging.getShowCauses() && exception.getCause() != null) {
+            printException(descriptor, exception.getCause(), stackTrace, exceptionLevel + 1, builder);
+        }
+    }
+
+    private List<StackTraceElement> filterStackTrace(Throwable exception, TestDescriptor descriptor) {
+        Spec<StackTraceElement> filterSpec = createCompositeFilter(descriptor);
+        StackTraceFilter filter = new StackTraceFilter(filterSpec);
+        return filter.filter(exception);
+    }
+
+    private Spec<StackTraceElement> createCompositeFilter(TestDescriptor descriptor) {
+        List<Spec<StackTraceElement>> filters = Lists.newArrayList();
+        for (TestStackTraceFilter type : testLogging.getStackTraceFilters()) {
+            filters.add(createFilter(descriptor, type));
+        }
+        return new AndSpec<StackTraceElement>(filters);
+    }
+
+    private Spec<StackTraceElement> createFilter(TestDescriptor descriptor, TestStackTraceFilter filterType) {
+        switch (filterType) {
+            case ENTRY_POINT:
+                return new ClassMethodNameStackTraceSpec(descriptor.getClassName(), descriptor.getName());
+            case TRUNCATE:
+                return new TruncatedStackTraceSpec(new ClassMethodNameStackTraceSpec(descriptor.getClassName(), null));
+            case GROOVY:
+                return new GroovyStackTraceSpec();
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    private int countCommonElements(List<StackTraceElement> stackTrace, @Nullable List<StackTraceElement> parentTrace) {
+        if (parentTrace == null) {
+            return 0;
+        }
+
+        int commonElements = 0;
+        for (int i = stackTrace.size() - 1, j = parentTrace.size() - 1;
+            // i >= 1 makes sure that commonElements < stackTrace.size()
+             i >= 1 && j >= 0 && stackTrace.get(i).equals(parentTrace.get(j)); i--, j--) {
+            commonElements++;
+        }
+        return commonElements;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/GroovyStackTraceSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/GroovyStackTraceSpec.java
new file mode 100644
index 0000000..9f9f751
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/GroovyStackTraceSpec.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import org.gradle.api.specs.Spec;
+
+import java.util.regex.Pattern;
+
+// implementation based on Spock's StackTraceFilter class
+public class GroovyStackTraceSpec implements Spec<StackTraceElement> {
+    private static final Pattern INTERNAL_CLASSES = Pattern.compile(
+            "org.codehaus.groovy.runtime\\..*"
+                    + "|org.codehaus.groovy.reflection\\..*"
+                    + "|org.codehaus.groovy\\..*MetaClass.*"
+                    + "|groovy\\..*MetaClass.*"
+                    + "|groovy.lang.MetaMethod"
+                    + "|java.lang.reflect\\..*"
+                    + "|sun.reflect\\..*"
+    );
+
+    public boolean isSatisfiedBy(StackTraceElement element) {
+        return !isInternalClass(element) && !isGeneratedMethod(element);
+    }
+
+    private boolean isInternalClass(StackTraceElement element) {
+        return INTERNAL_CLASSES.matcher(element.getClassName()).matches();
+    }
+
+    private boolean isGeneratedMethod(StackTraceElement element) {
+        return element.getLineNumber() < 0;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatter.java
new file mode 100644
index 0000000..d613d5a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import com.google.common.base.Strings;
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.logging.TestLogging;
+import org.gradle.messaging.remote.internal.PlaceholderException;
+
+import java.util.List;
+
+public class ShortExceptionFormatter implements TestExceptionFormatter {
+    private static final String INDENT = "    ";
+
+    private final TestLogging testLogging;
+
+    public ShortExceptionFormatter(TestLogging testLogging) {
+        this.testLogging = testLogging;
+    }
+
+    public String format(TestDescriptor descriptor, List<Throwable> exceptions) {
+        StringBuilder builder = new StringBuilder();
+        for (Throwable exception : exceptions) {
+            printException(descriptor, exception, false, 1, builder);
+        }
+        return builder.toString();
+    }
+
+    private void printException(TestDescriptor descriptor, Throwable exception, boolean cause, int indentLevel, StringBuilder builder) {
+        String indent = Strings.repeat(INDENT, indentLevel);
+        builder.append(indent);
+        if (cause) {
+            builder.append("Caused by: ");
+        }
+        String className = exception instanceof PlaceholderException
+                ? ((PlaceholderException) exception).getExceptionClassName() : exception.getClass().getName();
+        builder.append(className);
+
+        StackTraceFilter filter = new StackTraceFilter(new ClassMethodNameStackTraceSpec(descriptor.getClassName(), null));
+        List<StackTraceElement> stackTrace = filter.filter(exception);
+        if (stackTrace.size() > 0) {
+            StackTraceElement element = stackTrace.get(0);
+            builder.append(" at ");
+            builder.append(element.getFileName());
+            builder.append(':');
+            builder.append(element.getLineNumber());
+        }
+        builder.append('\n');
+
+        if (testLogging.getShowCauses() && exception.getCause() != null) {
+            printException(descriptor, exception.getCause(), true, indentLevel + 1, builder);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StackTraceFilter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StackTraceFilter.java
new file mode 100644
index 0000000..087914b
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StackTraceFilter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import com.google.common.collect.Lists;
+import org.gradle.api.specs.Spec;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class StackTraceFilter {
+    private final Spec<StackTraceElement> filterSpec;
+
+    public StackTraceFilter(Spec<StackTraceElement> filterSpec) {
+        this.filterSpec = filterSpec;
+    }
+
+    // stack traces are filtered in call order (from bottom to top)
+    public List<StackTraceElement> filter(List<StackTraceElement> stackTrace) {
+        List<StackTraceElement> filtered = Lists.newArrayList();
+        for (StackTraceElement element : Lists.reverse(stackTrace)) {
+            if (filterSpec.isSatisfiedBy(element)) {
+                filtered.add(element);
+            }
+        }
+        return Lists.reverse(filtered);
+    }
+
+    public List<StackTraceElement> filter(Throwable throwable) {
+        return filter(Arrays.asList(throwable.getStackTrace()));
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLogger.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLogger.java
deleted file mode 100644
index 84b08ee..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLogger.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal.tasks.testing.logging;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.tasks.testing.TestDescriptor;
-import org.gradle.api.tasks.testing.TestLogging;
-import org.gradle.api.tasks.testing.TestOutputEvent;
-import org.gradle.api.tasks.testing.TestOutputListener;
-import org.slf4j.Logger;
-
-/**
- * Test output listener that logs extra stuff based on the logging configuration
- * <p>
- * by Szczepan Faber, created at: 10/31/11
- */
-public class StandardStreamsLogger implements TestOutputListener {
-    private final Logger logger;
-    private final TestLogging logging;
-    private TestDescriptor currentTestDescriptor;
-
-    public StandardStreamsLogger(Logger logger, TestLogging logging) {
-        this.logger = logger;
-        this.logging = logging;
-    }
-
-    public void onOutput(TestDescriptor testDescriptor, TestOutputEvent outputEvent) {
-        if (logging.getShowStandardStreams()) {
-            if (!testDescriptor.equals(currentTestDescriptor)) {
-                currentTestDescriptor = testDescriptor;
-                logger.info(StringUtils.capitalize(testDescriptor.toString() + " output:"));
-            }
-            if (outputEvent.getDestination() == TestOutputEvent.Destination.StdOut) {
-                logger.info(outputEvent.getMessage());
-            } else if (outputEvent.getDestination() == TestOutputEvent.Destination.StdErr) {
-                logger.error(outputEvent.getMessage());
-            }
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestCountLogger.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestCountLogger.java
new file mode 100644
index 0000000..527cb9f
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestCountLogger.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.TestListener;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.TextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestCountLogger implements TestListener {
+    private final ProgressLoggerFactory factory;
+    private ProgressLogger progressLogger;
+    private final Logger logger;
+
+    private long totalTests;
+    private long failedTests;
+    private long skippedTests;
+    private boolean hadFailures;
+
+    public TestCountLogger(ProgressLoggerFactory factory) {
+        this(factory, LoggerFactory.getLogger(TestCountLogger.class));
+    }
+
+    TestCountLogger(ProgressLoggerFactory factory, Logger logger) {
+        this.factory = factory;
+        this.logger = logger;
+    }
+
+    public void beforeTest(TestDescriptor testDescriptor) {
+    }
+
+    public void afterTest(TestDescriptor testDescriptor, TestResult result) {
+        totalTests += result.getTestCount();
+        failedTests += result.getFailedTestCount();
+        skippedTests += result.getSkippedTestCount();
+        progressLogger.progress(summary());
+    }
+
+    private String summary() {
+        StringBuilder builder = new StringBuilder();
+        append(builder, totalTests, "test");
+        builder.append(" completed");
+        if (failedTests > 0) {
+            builder.append(", ");
+            builder.append(failedTests);
+            builder.append(" failed");
+        }
+        if (skippedTests > 0) {
+            builder.append(", ");
+            builder.append(skippedTests);
+            builder.append(" skipped");
+        }
+        return builder.toString();
+    }
+
+    private void append(StringBuilder dest, long count, String noun) {
+        dest.append(count);
+        dest.append(" ");
+        dest.append(noun);
+        if (count != 1) {
+            dest.append("s");
+        }
+    }
+
+    public void beforeSuite(TestDescriptor suite) {
+        if (suite.getParent() == null) {
+            progressLogger = factory.newOperation(TestCountLogger.class);
+            progressLogger.setDescription("Run tests");
+            progressLogger.started();
+        }
+    }
+
+    public void afterSuite(TestDescriptor suite, TestResult result) {
+        if (suite.getParent() == null) {
+            if (failedTests > 0) {
+                logger.error(TextUtil.getPlatformLineSeparator() + summary());
+            }
+            progressLogger.completed();
+
+            if (result.getResultType() == TestResult.ResultType.FAILURE) {
+                hadFailures = true;
+            }
+        }
+    }
+
+    public boolean hadFailures() {
+        return hadFailures;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLogger.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLogger.java
new file mode 100644
index 0000000..fb91fd5
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLogger.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.tasks.testing.*;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.api.tasks.testing.logging.TestLogEvent;
+import org.gradle.api.tasks.testing.logging.TestLogging;
+import org.gradle.logging.StyledTextOutputFactory;
+import org.gradle.util.TextUtil;
+
+/**
+ * Console logger for test events.
+ */
+public class TestEventLogger extends AbstractTestLogger implements TestListener, TestOutputListener {
+    private static final String INDENT = "    ";
+
+    private final TestExceptionFormatter exceptionFormatter;
+    private final TestLogging testLogging;
+
+    public TestEventLogger(StyledTextOutputFactory textOutputFactory, LogLevel logLevel, TestLogging testLogging, TestExceptionFormatter exceptionFormatter) {
+        super(textOutputFactory, logLevel, testLogging.getDisplayGranularity());
+        this.exceptionFormatter = exceptionFormatter;
+        this.testLogging = testLogging;
+    }
+
+    public void beforeSuite(TestDescriptor descriptor) {
+        before(descriptor);
+    }
+
+    public void afterSuite(TestDescriptor descriptor, TestResult result) {
+        after(descriptor, result);
+    }
+
+    public void beforeTest(TestDescriptor descriptor) {
+        before(descriptor);
+    }
+
+    public void afterTest(TestDescriptor descriptor, TestResult result) {
+        after(descriptor, result);
+    }
+
+    public void onOutput(TestDescriptor descriptor, TestOutputEvent outputEvent) {
+        if (outputEvent.getDestination() == TestOutputEvent.Destination.StdOut
+                && isLoggedEventType(TestLogEvent.STANDARD_OUT)) {
+            logEvent(descriptor, TestLogEvent.STANDARD_OUT, TextUtil.indent(outputEvent.getMessage(), INDENT) + "\n");
+        } else if (outputEvent.getDestination() == TestOutputEvent.Destination.StdErr
+                && isLoggedEventType(TestLogEvent.STANDARD_ERROR)) {
+            logEvent(descriptor, TestLogEvent.STANDARD_ERROR, TextUtil.indent(outputEvent.getMessage(), INDENT) + "\n");
+        }
+    }
+
+    private void before(TestDescriptor descriptor) {
+        if (shouldLogEvent(descriptor, TestLogEvent.STARTED)) {
+            logEvent(descriptor, TestLogEvent.STARTED);
+        }
+    }
+
+    private void after(TestDescriptor descriptor, TestResult result) {
+        TestLogEvent event = getEvent(result);
+
+        if (shouldLogEvent(descriptor, event)) {
+            String details = shouldLogExceptions(result) ? exceptionFormatter.format(descriptor, result.getExceptions()) : null;
+            logEvent(descriptor, event, details);
+        }
+    }
+
+    private TestLogEvent getEvent(TestResult result) {
+        switch (result.getResultType()) {
+            case SUCCESS: return TestLogEvent.PASSED;
+            case FAILURE: return TestLogEvent.FAILED;
+            case SKIPPED: return TestLogEvent.SKIPPED;
+            default: throw new AssertionError();
+        }
+    }
+
+    private boolean shouldLogEvent(TestDescriptor descriptor, TestLogEvent event) {
+        return isLoggedGranularity(descriptor) && isLoggedEventType(event);
+    }
+
+    private boolean shouldLogExceptions(TestResult result) {
+        return testLogging.getShowExceptions() && !result.getExceptions().isEmpty();
+    }
+
+    private boolean isLoggedGranularity(TestDescriptor descriptor) {
+        int level = getLevel(descriptor);
+        return ((testLogging.getMinGranularity() == -1 && !descriptor.isComposite())
+                || testLogging.getMinGranularity() > -1 && level >= testLogging.getMinGranularity())
+            && (testLogging.getMaxGranularity() == -1 || level <= testLogging.getMaxGranularity());
+    }
+
+    private int getLevel(TestDescriptor descriptor) {
+        int level = 0;
+        while (descriptor.getParent() != null) {
+            level++;
+            descriptor = descriptor.getParent();
+        }
+        return level;
+    }
+
+    private boolean isLoggedEventType(TestLogEvent event) {
+        return testLogging.getEvents().contains(event);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestExceptionFormatter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestExceptionFormatter.java
new file mode 100644
index 0000000..8d5e59a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TestExceptionFormatter.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import org.gradle.api.tasks.testing.TestDescriptor;
+
+import java.util.*;
+
+public interface TestExceptionFormatter {
+    String format(TestDescriptor descriptor, List<Throwable> exceptions);
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TruncatedStackTraceSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TruncatedStackTraceSpec.java
new file mode 100644
index 0000000..1910c5a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/logging/TruncatedStackTraceSpec.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging;
+
+import org.gradle.api.specs.Spec;
+
+public class TruncatedStackTraceSpec implements Spec<StackTraceElement> {
+    private final Spec<StackTraceElement> truncationPointDetector;
+    private boolean truncationPointReached;
+
+    TruncatedStackTraceSpec(Spec<StackTraceElement> truncationPointDetector) {
+        this.truncationPointDetector = truncationPointDetector;
+    }
+
+    public boolean isSatisfiedBy(StackTraceElement element) {
+        return truncationPointReached |= truncationPointDetector.isSatisfiedBy(element);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java
index ecfdd3e..e1356fe 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java
@@ -19,7 +19,7 @@ package org.gradle.api.internal.tasks.testing.processors;
 import org.gradle.api.internal.tasks.testing.*;
 import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 public class TestMainAction implements Runnable {
     private final TestClassProcessor processor;
@@ -51,7 +51,7 @@ public class TestMainAction implements Runnable {
 
     private static class RootTestSuiteDescriptor extends DefaultTestSuiteDescriptor {
         public RootTestSuiteDescriptor() {
-            super("root", "");
+            super("root", "Test Run");
         }
 
         @Override
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java
deleted file mode 100644
index 6675967..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal.tasks.testing.results;
-
-import org.gradle.api.internal.tasks.testing.*;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
-import org.gradle.api.tasks.testing.TestOutputEvent;
-
-public class LoggingResultProcessor implements TestResultProcessor {
-    private static final Logger LOGGER = Logging.getLogger(LoggingResultProcessor.class);
-    private final String prefix;
-    private final TestResultProcessor processor;
-
-    public LoggingResultProcessor(String prefix, TestResultProcessor processor) {
-        this.prefix = prefix;
-        this.processor = processor;
-    }
-
-    public void started(TestDescriptorInternal test, TestStartEvent event) {
-        LOGGER.lifecycle("{} START {} {}", prefix, test.getId(), test);
-        processor.started(test, event);
-    }
-
-    public void failure(Object testId, Throwable result) {
-        LOGGER.lifecycle("{} FAILED {}", prefix, testId);
-        processor.failure(testId, result);
-    }
-
-    public void completed(Object testId, TestCompleteEvent event) {
-        LOGGER.lifecycle("{} COMPLETED {} {}", prefix, testId, event.getResultType());
-        processor.completed(testId, event);
-    }
-
-    public void output(Object testId, TestOutputEvent event) {
-        LOGGER.lifecycle("{} OUTPUT {} {} [{}]", prefix, testId, event.getDestination(), event.getMessage());
-        processor.output(testId, event);
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestLogger.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestLogger.java
deleted file mode 100644
index 7ef1d6a..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestLogger.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal.tasks.testing.results;
-
-import org.gradle.api.tasks.testing.TestDescriptor;
-import org.gradle.api.tasks.testing.TestListener;
-import org.gradle.api.tasks.testing.TestResult;
-import org.gradle.logging.ProgressLogger;
-import org.gradle.logging.ProgressLoggerFactory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class TestLogger implements TestListener {
-    private final ProgressLoggerFactory factory;
-    private final Logger logger;
-    private ProgressLogger progressLogger;
-    private long totalTests;
-    private long failedTests;
-
-    public TestLogger(ProgressLoggerFactory factory) {
-        this(factory, LoggerFactory.getLogger(TestLogger.class));
-    }
-
-    TestLogger(ProgressLoggerFactory factory, Logger logger) {
-        this.factory = factory;
-        this.logger = logger;
-    }
-
-    public void beforeTest(TestDescriptor testDescriptor) {
-    }
-
-    public void afterTest(TestDescriptor testDescriptor, TestResult result) {
-        totalTests += result.getTestCount();
-        failedTests += result.getFailedTestCount();
-        progressLogger.progress(summary());
-    }
-
-    private String summary() {
-        StringBuilder builder = new StringBuilder();
-        append(builder, totalTests, "test");
-        builder.append(" completed");
-        if (failedTests > 0) {
-            builder.append(", ");
-            append(builder, failedTests, "failure");
-        }
-        return builder.toString();
-    }
-
-    private void append(StringBuilder dest, long count, String noun) {
-        dest.append(count);
-        dest.append(" ");
-        dest.append(noun);
-        if (count != 1) {
-            dest.append("s");
-        }
-    }
-
-    public void beforeSuite(TestDescriptor suite) {
-        if (suite.getParent() == null) {
-            progressLogger = factory.newOperation(TestLogger.class);
-            progressLogger.setDescription("Run tests");
-            progressLogger.started();
-        }
-    }
-
-    public void afterSuite(TestDescriptor suite, TestResult result) {
-        if (suite.getParent() == null) {
-            if (failedTests > 0) {
-                logger.error(summary());
-            }
-            progressLogger.completed();
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListener.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListener.java
deleted file mode 100755
index 6795b4c..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListener.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal.tasks.testing.results;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.internal.tasks.testing.TestSuiteExecutionException;
-import org.gradle.api.tasks.testing.TestDescriptor;
-import org.gradle.api.tasks.testing.TestListener;
-import org.gradle.api.tasks.testing.TestResult;
-import org.slf4j.Logger;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.gradle.api.tasks.testing.TestResult.*;
-
-public class TestSummaryListener implements TestListener {
-    private final Logger logger;
-    private boolean hadFailures;
-    private final Set<String> failedClasses = new HashSet<String>();
-
-    public TestSummaryListener(Logger logger) {
-        this.logger = logger;
-    }
-
-    public boolean hadFailures() {
-        return hadFailures;
-    }
-
-    public void beforeSuite(TestDescriptor suite) {
-        logger.debug("Started {}", suite);
-    }
-
-    public void afterSuite(TestDescriptor suite, TestResult result) {
-        if (result.getResultType() == ResultType.FAILURE && result.getException() != null) {
-            reportFailure(suite, toString(suite), result);
-        } else {
-            logger.debug("Finished {}", suite);
-        }
-        if (suite.getParent() == null && result.getResultType() == ResultType.FAILURE) {
-            hadFailures = true;
-        }
-    }
-
-    public void beforeTest(TestDescriptor testDescriptor) {
-        logger.debug("Started {}", testDescriptor);
-    }
-
-    public void afterTest(TestDescriptor testDescriptor, TestResult result) {
-        String testDescription = toString(testDescriptor);
-        switch (result.getResultType()) {
-            case SUCCESS:
-                logger.info("{} PASSED", testDescription);
-                break;
-            case SKIPPED:
-                logger.info("{} SKIPPED", testDescription);
-                break;
-            case FAILURE:
-                reportFailure(testDescriptor, testDescription, result);
-                break;
-            default:
-                throw new IllegalArgumentException();
-        }
-    }
-
-    private void reportFailure(TestDescriptor testDescriptor, String testDescription, TestResult result) {
-        String testClass = testDescriptor.getClassName();
-        if (result.getException() instanceof TestSuiteExecutionException) {
-            logger.error(String.format("Execution for %s FAILED", testDescription), result.getException());
-        } else if (testClass == null) {
-            logger.error("{} FAILED: {}", testDescription, result.getException());
-        } else {
-            logger.info("{} FAILED: {}", testDescription, result.getException());
-            if (failedClasses.add(testClass)) {
-                logger.error("Test {} FAILED", testClass);
-            }
-        }
-    }
-
-    private String toString(TestDescriptor testDescriptor) {
-        return StringUtils.capitalize(testDescriptor.toString());
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
index 0e5a681..51de906 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
@@ -27,7 +27,7 @@ import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.logging.StandardOutputRedirector;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.GUtil;
-import org.gradle.util.IdGenerator;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.util.ReflectionUtil;
 import org.testng.ITestListener;
 import org.testng.TestNG;
@@ -72,6 +72,8 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
         testNg.setOutputDirectory(testReportDir.getAbsolutePath());
         testNg.setDefaultSuiteName(options.getSuiteName());
         testNg.setDefaultTestName(options.getTestName());
+        testNg.setParallel(options.getParallel());
+        testNg.setThreadCount(options.getThreadCount());
         try {
             ReflectionUtil.invoke(testNg, "setAnnotations", options.getAnnotations());
         } catch (MissingMethodException e) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
index 3745bc4..89a22ce 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.testing.testng;
 
 import org.gradle.api.Action;
 import org.gradle.api.JavaVersion;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestFramework;
@@ -27,7 +28,6 @@ import org.gradle.api.internal.tasks.testing.junit.JULRedirector;
 import org.gradle.api.tasks.testing.Test;
 import org.gradle.api.tasks.testing.testng.TestNGOptions;
 import org.gradle.process.internal.WorkerProcessBuilder;
-import org.gradle.util.IdGenerator;
 
 import java.io.File;
 import java.io.Serializable;
@@ -46,7 +46,7 @@ public class TestNGTestFramework implements TestFramework {
         this.testTask = testTask;
         options = new TestNGOptions(testTask.getProject().getProjectDir());
         options.setAnnotationsOnSourceCompatibility(JavaVersion.toVersion(testTask.getProject().property("sourceCompatibility")));
-        detector = new TestNGDetector(testTask.getTestClassesDir(), testTask.getClasspath(), new ClassFileExtractionManager(testTask.getTemporaryDir()));
+        detector = new TestNGDetector(testTask.getTestClassesDir(), testTask.getClasspath(), new ClassFileExtractionManager(testTask.getTemporaryDirFactory()));
     }
 
     public WorkerTestClassProcessorFactory getProcessorFactory() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
index 964d307..b669ac4 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
@@ -19,7 +19,7 @@ package org.gradle.api.internal.tasks.testing.testng;
 import com.google.common.collect.Maps;
 import org.gradle.api.internal.tasks.testing.*;
 import org.gradle.api.tasks.testing.TestResult;
-import org.gradle.util.IdGenerator;
+import org.gradle.internal.id.IdGenerator;
 import org.testng.ITestContext;
 import org.testng.ITestListener;
 import org.testng.ITestNGMethod;
@@ -128,7 +128,7 @@ public class TestNGTestResultProcessorAdapter implements ITestListener, TestNGCo
 
     public void onConfigurationFailure(ITestResult testResult) {
         if (failedConfigurations.put(testResult, true) != null) {
-            // work around for bug in TestNG 6.2+: listener is notified twice per event
+            // workaround for bug in TestNG 6.2 (apparently fixed in some 6.3.x): listener is notified twice per event
             return;
         }
         // Synthesise a test for the broken configuration method
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
index 156a224..b75c3d0 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
@@ -21,20 +21,20 @@ import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+import org.gradle.internal.TrueTimeProvider;
 import org.gradle.internal.UncheckedException;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.id.CompositeIdGenerator;
+import org.gradle.internal.id.IdGenerator;
+import org.gradle.internal.id.LongIdGenerator;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ContextClassLoaderProxy;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.messaging.actor.internal.DefaultActorFactory;
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
-import org.gradle.messaging.concurrent.ExecutorFactory;
 import org.gradle.messaging.remote.ObjectConnection;
 import org.gradle.process.internal.WorkerProcessContext;
-import org.gradle.util.CompositeIdGenerator;
-import org.gradle.util.IdGenerator;
-import org.gradle.util.LongIdGenerator;
-import org.gradle.util.TrueTimeProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -93,10 +93,12 @@ public class TestWorker implements Action<WorkerProcessContext>, RemoteTestClass
     }
 
     public void startProcessing() {
+        Thread.currentThread().setName("Test worker");
         processor.startProcessing(resultProcessor);
     }
 
     public void processTestClass(final TestClassRunInfo testClass) {
+        Thread.currentThread().setName("Test worker");
         try {
             processor.processTestClass(testClass);
         } finally {
@@ -106,6 +108,7 @@ public class TestWorker implements Action<WorkerProcessContext>, RemoteTestClass
     }
 
     public void stop() {
+        Thread.currentThread().setName("Test worker");
         try {
             processor.stop();
         } finally {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java
index b138b6d..ad12819 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.tasks.testing.worker;
 
 import org.gradle.api.internal.tasks.testing.*;
-import org.gradle.util.TimeProvider;
+import org.gradle.internal.TimeProvider;
 
 public class WorkerTestClassProcessor extends SuiteTestClassProcessor {
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy
deleted file mode 100644
index 841ac24..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.plugins
-
-import org.apache.commons.lang.StringUtils
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.gradle.api.Rule
-import org.gradle.api.Task
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.ConfigurationContainer
-import org.gradle.api.artifacts.Dependency
-import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet
-import org.gradle.api.tasks.Delete
-import org.gradle.api.tasks.Upload
-import org.gradle.api.tasks.bundling.AbstractArchiveTask
-import org.gradle.api.tasks.bundling.Jar
-
-/**
- * <p>A  {@link org.gradle.api.Plugin}  which defines a basic project lifecycle and some common convention properties.</p>
- */
-class BasePlugin implements Plugin<Project> {
-    public static final String CLEAN_TASK_NAME = 'clean'
-    public static final String ASSEMBLE_TASK_NAME = 'assemble'
-    public static final String BUILD_GROUP = 'build'
-    public static final String UPLOAD_GROUP = 'upload'
-
-    public void apply(Project project) {
-        def convention = new BasePluginConvention(project)
-        project.convention.plugins.base = convention
-
-        configureBuildConfigurationRule(project)
-        configureUploadRules(project)
-        configureArchiveDefaults(project, convention)
-        configureConfigurations(project)
-
-        addClean(project)
-        addCleanRule(project)
-        addAssemble(project);
-    }
-
-    private Task addAssemble(Project project) {
-        Task assembleTask = project.tasks.add(ASSEMBLE_TASK_NAME);
-        assembleTask.description = "Assembles all Jar, War, Zip, and Tar archives.";
-        assembleTask.group = BUILD_GROUP
-        assembleTask.dependsOn project.configurations[Dependency.ARCHIVES_CONFIGURATION].allArtifacts.buildDependencies
-    }
-
-    private void configureArchiveDefaults(Project project, BasePluginConvention pluginConvention) {
-        project.tasks.withType(AbstractArchiveTask) {AbstractArchiveTask task ->
-            if (task instanceof Jar) {
-                task.conventionMapping.destinationDir = { pluginConvention.libsDir }
-            } else {
-                task.conventionMapping.destinationDir = { pluginConvention.distsDir }
-            }
-            task.conventionMapping.version = { project.version == Project.DEFAULT_VERSION ? null : project.version.toString() }
-            task.conventionMapping.baseName = { pluginConvention.archivesBaseName }
-        }
-    }
-
-    private void addClean(final Project project) {
-        Delete clean = project.tasks.add(CLEAN_TASK_NAME, Delete.class)
-        clean.description = "Deletes the build directory.";
-        clean.group = BUILD_GROUP
-        clean.delete { project.buildDir }
-    }
-
-    private void addCleanRule(Project project) {
-        String prefix = 'clean'
-        String description = "Pattern: ${prefix}<TaskName>: Cleans the output files of a task."
-        Rule rule = [
-                getDescription: { description },
-                apply: {String taskName ->
-                    if (!taskName.startsWith(prefix)) {
-                        return
-                    }
-                    String targetTaskName = taskName.substring(prefix.length())
-                    if (targetTaskName.charAt(0).isLowerCase()) {
-                        return
-                    }
-                    Task task = project.tasks.findByName(StringUtils.uncapitalize(targetTaskName))
-                    if (task == null) {
-                        return
-                    }
-                    Delete clean = project.tasks.add(taskName, Delete)
-                    clean.delete(task.outputs.files)
-                },
-                toString: { "Rule: " + description }
-        ] as Rule
-
-        project.tasks.addRule(rule)
-    }
-
-    private void configureBuildConfigurationRule(Project project) {
-        String prefix = "build";
-        String description = "Pattern: ${prefix}<ConfigurationName>: Assembles the artifacts of a configuration."
-        Rule rule = [
-                getDescription: {
-                    description
-                },
-                apply: {String taskName ->
-                    if (taskName.startsWith(prefix)) {
-                        Configuration configuration = project.configurations.findByName(StringUtils.uncapitalize(taskName.substring(prefix.length())))
-                        if (configuration != null) {
-                            project.tasks.add(taskName).dependsOn(configuration.getAllArtifacts()).setDescription(String.format("Builds the artifacts belonging to %s.", configuration))
-                        }
-                    }
-                },
-                toString: { "Rule: " + description }
-        ] as Rule
-
-        project.configurations.all {
-            if (!project.tasks.rules.contains(rule)) {
-                project.tasks.addRule(rule)
-            }
-        }
-    }
-
-    private void configureUploadRules(final Project project) {
-        String description = "Pattern: upload<ConfigurationName>: Assembles and uploads the artifacts belonging to a configuration."
-        Rule rule = [
-                getDescription: {
-                    description
-                },
-                apply: {String taskName ->
-                    Set<Configuration> configurations = project.configurations
-                    for (Configuration configuration: configurations) {
-                        if (taskName == configuration.uploadTaskName) {
-                            createUploadTask(configuration.uploadTaskName, configuration, project)
-                        }
-                    }
-                },
-                toString: { "Rule: " + description }
-        ] as Rule
-        project.configurations.all {
-            if (!project.tasks.rules.contains(rule)) {
-                project.tasks.addRule(rule)
-            }
-        }
-    }
-
-    private Upload createUploadTask(String name, final Configuration configuration, Project project) {
-        Upload upload = project.getTasks().add(name, Upload.class)
-        upload.configuration = configuration
-        upload.uploadDescriptor = true
-        upload.descriptorDestination = new File(project.getBuildDir(), "ivy.xml")
-        upload.description = "Uploads all artifacts belonging to $configuration."
-        upload.group = UPLOAD_GROUP
-        return upload
-    }
-
-    private void configureConfigurations(final Project project) {
-        ConfigurationContainer configurations = project.getConfigurations();
-        project.setProperty("status", "integration");
-
-        Configuration archivesConfiguration = configurations.add(Dependency.ARCHIVES_CONFIGURATION).
-                setDescription("Configuration for archive artifacts.");
-
-        configurations.add(Dependency.DEFAULT_CONFIGURATION).
-                setDescription("Configuration for default artifacts.");
-
-        def defaultArtifacts = project.extensions.create("defaultArtifacts", DefaultArtifactPublicationSet, archivesConfiguration.artifacts)
-
-        configurations.all {
-            artifacts.all { artifact ->
-                defaultArtifacts.addCandidate(artifact)
-            }
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.java
new file mode 100644
index 0000000..94bfd51
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2010 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 org.gradle.api.plugins;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.plugins.BuildConfigurationRule;
+import org.gradle.api.internal.plugins.CleanRule;
+import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
+import org.gradle.api.internal.plugins.UploadRule;
+import org.gradle.api.tasks.Delete;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.api.tasks.bundling.Jar;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+/**
+ * <p>A  {@link org.gradle.api.Plugin}  which defines a basic project lifecycle and some common convention properties.</p>
+ */
+public class BasePlugin implements Plugin<Project> {
+    public static final String CLEAN_TASK_NAME = "clean";
+    public static final String ASSEMBLE_TASK_NAME = "assemble";
+    public static final String BUILD_GROUP = "build";
+    public static final String UPLOAD_GROUP = "upload";
+
+    public void apply(Project project) {
+        BasePluginConvention convention = new BasePluginConvention(project);
+        project.getConvention().getPlugins().put("base", convention);
+
+        configureBuildConfigurationRule(project);
+        configureUploadRules(project);
+        configureArchiveDefaults(project, convention);
+        configureConfigurations(project);
+
+        addClean(project);
+        addCleanRule(project);
+        addAssemble(project);
+    }
+
+    private void addAssemble(Project project) {
+        Task assembleTask = project.getTasks().add(ASSEMBLE_TASK_NAME);
+        assembleTask.setDescription("Assembles all Jar, War, Zip, and Tar archives.");
+        assembleTask.setGroup(BUILD_GROUP);
+        assembleTask.dependsOn(project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION).getAllArtifacts().getBuildDependencies());
+    }
+
+    private void configureArchiveDefaults(final Project project, final BasePluginConvention pluginConvention) {
+        project.getTasks().withType(AbstractArchiveTask.class, new Action<AbstractArchiveTask>() {
+            public void execute(AbstractArchiveTask task) {
+                ConventionMapping taskConventionMapping = task.getConventionMapping();
+
+                Callable<File> destinationDir;
+                if (task instanceof Jar) {
+                    destinationDir = new Callable<File>() {
+                        public File call() throws Exception {
+                            return pluginConvention.getLibsDir();
+                        }
+                    };
+                } else {
+                    destinationDir = new Callable<File>() {
+                        public File call() throws Exception {
+                            return pluginConvention.getDistsDir();
+                        }
+                    };
+                }
+                taskConventionMapping.map("destinationDir", destinationDir);
+
+                taskConventionMapping.map("version", new Callable<String>() {
+                    public String call() throws Exception {
+                        return project.getVersion() == Project.DEFAULT_VERSION ? null : project.getVersion().toString();
+                    }
+                });
+
+                taskConventionMapping.map("baseName", new Callable<String>() {
+                    public String call() throws Exception {
+                        return pluginConvention.getArchivesBaseName();
+                    }
+                });
+            }
+        });
+    }
+
+    private void addClean(final Project project) {
+        Delete clean = project.getTasks().add(CLEAN_TASK_NAME, Delete.class);
+        clean.setDescription("Deletes the build directory.");
+        clean.setGroup(BUILD_GROUP);
+        clean.delete(new Callable<File>() {
+            public File call() throws Exception {
+                return project.getBuildDir();
+            }
+        });
+    }
+
+    private void addCleanRule(Project project) {
+        project.getTasks().addRule(new CleanRule(project.getTasks()));
+    }
+
+    private void configureBuildConfigurationRule(Project project) {
+        project.getTasks().addRule(new BuildConfigurationRule(project.getConfigurations(), project.getTasks()));
+    }
+
+    private void configureUploadRules(final Project project) {
+        project.getTasks().addRule(new UploadRule(project));
+    }
+
+    private void configureConfigurations(final Project project) {
+        ConfigurationContainer configurations = project.getConfigurations();
+        project.setProperty("status", "integration");
+
+        Configuration archivesConfiguration = configurations.add(Dependency.ARCHIVES_CONFIGURATION).
+                setDescription("Configuration for archive artifacts.");
+
+        configurations.add(Dependency.DEFAULT_CONFIGURATION).
+                setDescription("Configuration for default artifacts.");
+
+        final DefaultArtifactPublicationSet defaultArtifacts = project.getExtensions().create(
+                "defaultArtifacts", DefaultArtifactPublicationSet.class, archivesConfiguration.getArtifacts()
+        );
+
+        configurations.all(new Action<Configuration>() {
+            public void execute(Configuration configuration) {
+                configuration.getArtifacts().all(new Action<PublishArtifact>() {
+                    public void execute(PublishArtifact artifact) {
+                        defaultArtifacts.addCandidate(artifact);
+                    }
+                });
+            }
+        });
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
index bfc6454..86ef37b 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.plugins
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
 import org.gradle.api.file.SourceDirectorySet
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.tasks.DefaultSourceSetContainer
 import org.gradle.api.java.archives.Manifest
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
index b706d28..6c8f550 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
@@ -16,7 +16,7 @@
 package org.gradle.api.plugins;
 
 import org.gradle.api.Plugin;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.reporting.ReportingExtension;
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
index 4d47744..a141072 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/DefaultReportContainer.java
@@ -21,7 +21,7 @@ import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.NamedDomainObjectSet;
 import org.gradle.api.internal.ConfigureDelegate;
 import org.gradle.api.internal.DefaultNamedDomainObjectSet;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.reporting.Report;
 import org.gradle.api.reporting.ReportContainer;
 import org.gradle.api.specs.Spec;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java
index 6e2f6d5..02b1605 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/reporting/internal/TaskReportContainer.java
@@ -18,7 +18,7 @@ package org.gradle.api.reporting.internal;
 
 import org.gradle.api.Task;
 import org.gradle.api.Transformer;
-import org.gradle.api.internal.Instantiator;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.TaskInternal;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.reporting.Report;
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.java
index 1f6df6e..47aced2 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.java
@@ -19,7 +19,7 @@ package org.gradle.api.tasks.compile;
 import com.google.common.collect.Maps;
 import org.gradle.api.Nullable;
 import org.gradle.internal.UncheckedException;
-import org.gradle.util.JavaReflectionUtil;
+import org.gradle.internal.reflect.JavaReflectionUtil;
 
 import java.io.Serializable;
 import java.lang.reflect.Field;
@@ -40,7 +40,7 @@ public abstract class AbstractOptions implements Serializable {
     public void define(@Nullable Map<String, Object> args) {
         if (args == null) { return; }
         for (Map.Entry<String, Object> arg: args.entrySet()) {
-            JavaReflectionUtil.setProperty(this, arg.getKey(), arg.getValue());
+            JavaReflectionUtil.writeProperty(this, arg.getKey(), arg.getValue());
         }
     }
 
@@ -54,7 +54,7 @@ public abstract class AbstractOptions implements Serializable {
     }
 
     private void addValueToMapIfNotNull(Map<String, Object> map, Field field) {
-        Object value = JavaReflectionUtil.getProperty(this, field.getName());
+        Object value = JavaReflectionUtil.readProperty(this, field.getName());
         if (value != null) {
             map.put(antProperty(field.getName()), antValue(field.getName(), value));
         }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.java
index cac73b8..5ebd63e 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.java
@@ -36,324 +36,438 @@ import java.util.concurrent.Callable;
 public class CompileOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
 
-    /**
-     * Specifies whether the compile task should fail when compilation fails. The default is {@code true}.
-     */
-    @Input
     private boolean failOnError = true;
 
+    private boolean verbose;
+
+    private boolean listFiles;
+
+    private boolean deprecation;
+
+    private boolean warnings = true;
+
+    private String encoding;
+
+    private boolean optimize;
+
+    private boolean debug = true;
+
+    private DebugOptions debugOptions = new DebugOptions();
+
+    private boolean fork;
+
+    private ForkOptions forkOptions = new ForkOptions();
+
+    private boolean useDepend;
+
+    private DependOptions dependOptions = new DependOptions();
+
+    private String compiler;
+
+    private boolean includeJavaRuntime;
+
+    private String bootClasspath;
+
+    private String extensionDirs;
+
+    private List<String> compilerArgs = Lists.newArrayList();
+
+    private boolean useAnt;
+
+    /**
+     * Tells whether to fail the build when compilation fails. Defaults to {@code true}.
+     */
     public boolean isFailOnError() {
         return failOnError;
     }
 
+    /**
+     * Tells whether to fail the build when compilation fails. Defaults to {@code true}.
+     */
     // @Input not recognized if there is only an "is" method
+    @Input
     public boolean getFailOnError() {
         return failOnError;
     }
 
+    /**
+     * Sets whether to fail the build when compilation fails. Defaults to {@code true}.
+     */
     public void setFailOnError(boolean failOnError) {
         this.failOnError = failOnError;
     }
 
     /**
-     * Specifies whether the compile task should produce verbose output.
+     * Tells whether to produce verbose output. Defaults to {@code false}.
      */
-    private boolean verbose;
-
     public boolean isVerbose() {
         return verbose;
     }
 
+    /**
+     * Sets whether to produce verbose output. Defaults to {@code false}.
+     */
     public void setVerbose(boolean verbose) {
         this.verbose = verbose;
     }
 
     /**
-     * Specifies whether the compile task should list the files to be compiled.
+     * Tells whether to log the files to be compiled. Defaults to {@code false}.
      */
-    private boolean listFiles;
-
     public boolean isListFiles() {
         return listFiles;
     }
 
+    /**
+     * Sets whether to log the files to be compiled. Defaults to {@code false}.
+     */
     public void setListFiles(boolean listFiles) {
         this.listFiles = listFiles;
     }
 
     /**
-     * Specifies whether to log details of usage of deprecated members or classes. The default is {@code false}.
+     * Tells whether to log details of usage of deprecated members or classes. Defaults to {@code false}.
      */
-    private boolean deprecation;
-
     public boolean isDeprecation() {
         return deprecation;
     }
 
+    /**
+     * Sets whether to log details of usage of deprecated members or classes. Defaults to {@code false}.
+     */
     public void setDeprecation(boolean deprecation) {
         this.deprecation = deprecation;
     }
 
     /**
-     * Specifies whether to log warning messages. The default is {@code true}.
+     * Tells whether to log warning messages. The default is {@code true}.
      */
-    private boolean warnings = true;
-
     public boolean isWarnings() {
         return warnings;
     }
 
+    /**
+     * Sets whether to log warning messages. The default is {@code true}.
+     */
     public void setWarnings(boolean warnings) {
         this.warnings = warnings;
     }
 
     /**
-     * The source encoding name. Uses the platform default encoding if not specified. The default is {@code null}.
+     * Returns the character encoding to be used when reading source files. Defaults to {@code null}, in which
+     * case the platform default encoding will be used.
      */
-    @Input @Optional
-    private String encoding;
-
+    @Input
+    @Optional
     public String getEncoding() {
         return encoding;
     }
 
+    /**
+     * Sets the character encoding to be used when reading source files. Defaults to {@code null}, in which
+     * case the platform default encoding will be used.
+     */
     public void setEncoding(String encoding) {
         this.encoding = encoding;
     }
 
     /**
-     * Whether to produce optimized byte code. Note that this flag is ignored by Sun's javac starting with
-     * JDK 1.3 (since compile-time optimization is unnecessary)
+     * Tells whether to produce optimized byte code. Only takes effect if {@code useAnt} is {@code true}.
+     * Note that this flag is ignored by Sun's javac starting with JDK 1.3.
      */
-    @Input
-    private boolean optimize;
-
     public boolean isOptimize() {
         return optimize;
     }
 
+    /**
+     * Tells whether to produce optimized byte code. Only takes effect if {@code useAnt} is {@code true}.
+     * Note that this flag is ignored by Sun's javac starting with JDK 1.3.
+     */
     // @Input not recognized if there is only an "is" method
+    @Input
     public boolean getOptimize() {
         return optimize;
     }
 
+    /**
+     * Sets whether to produce optimized byte code. Only takes effect if {@code useAnt} is {@code true}.
+     * Note that this flag is ignored by Sun's javac starting with JDK 1.3.
+     */
     public void setOptimize(boolean optimize) {
         this.optimize = optimize;
     }
 
     /**
-     * Specifies whether debugging information should be included in the generated {@code .class} files. The default
-     * is {@code true}. See {@link DebugOptions#debugLevel} for which debugging information will be generated.
+     * Tells whether to include debugging information in the generated class files. Defaults
+     * to {@code true}. See {@link DebugOptions#debugLevel} for which debugging information will be generated.
      */
-    @Input
-    private boolean debug = true;
-
     public boolean isDebug() {
         return debug;
     }
 
+    /**
+     * Tells whether to include debugging information in the generated class files. Defaults
+     * to {@code true}. See {@link DebugOptions#debugLevel} for which debugging information will be generated.
+     */
     // @Input not recognized if there is only an "is" method
+    @Input
     public boolean getDebug() {
         return debug;
     }
 
+    /**
+     * Sets whether to include debugging information in the generated class files. Defaults
+     * to {@code true}. See {@link DebugOptions#debugLevel} for which debugging information will be generated.
+     */
     public void setDebug(boolean debug) {
         this.debug = debug;
     }
 
     /**
-     * Options for generating debugging information.
+     * Returns options for generating debugging information.
      */
     @Nested
-    private DebugOptions debugOptions = new DebugOptions();
-
     public DebugOptions getDebugOptions() {
         return debugOptions;
     }
 
+    /**
+     * Sets options for generating debugging information.
+     */
     public void setDebugOptions(DebugOptions debugOptions) {
         this.debugOptions = debugOptions;
     }
 
     /**
-     * Specifies whether to run the compiler in its own process. The default is {@code false}.
+     * Tells whether to run the compiler in its own process. Note that this does
+     * not necessarily mean that a new process will be created for each compile task.
+     * Defaults to {@code false}.
      */
-    private boolean fork;
-
     public boolean isFork() {
         return fork;
     }
 
+    /**
+     * Sets whether to run the compiler in its own process. Note that this does
+     * not necessarily mean that a new process will be created for each compile task.
+     * Defaults to {@code false}.
+     */
     public void setFork(boolean fork) {
         this.fork = fork;
     }
 
     /**
-     * The options for running the compiler in a child process.
+     * Returns options for running the compiler in a child process.
      */
     @Nested
-    private ForkOptions forkOptions = new ForkOptions();
-
     public ForkOptions getForkOptions() {
         return forkOptions;
     }
 
+    /**
+     * Sets options for running the compiler in a child process.
+     */
     public void setForkOptions(ForkOptions forkOptions) {
         this.forkOptions = forkOptions;
     }
 
     /**
-     * Specifies whether to use the Ant {@code <depend>} task.
+     * Tells whether to use the Ant {@code <depend>} task.
+     * Only takes effect if {@code useAnt} is {@code true}. Defaults to
+     * {@code false}.
      */
-    private boolean useDepend;
-
     public boolean isUseDepend() {
         return useDepend;
     }
 
+    /**
+     * Sets whether to use the Ant {@code <depend>} task.
+     * Only takes effect if {@code useAnt} is {@code true}. Defaults to
+     * {@code false}.
+     */
     public void setUseDepend(boolean useDepend) {
         this.useDepend = useDepend;
     }
 
     /**
-     * The options for using the Ant {@code <depend>} task.
+     * Returns options for using the Ant {@code <depend>} task.
      */
-    private DependOptions dependOptions = new DependOptions();
-
     public DependOptions getDependOptions() {
         return dependOptions;
     }
 
+    /**
+     * Sets options for using the Ant {@code <depend>} task.
+     */
     public void setDependOptions(DependOptions dependOptions) {
         this.dependOptions = dependOptions;
     }
 
     /**
-     * The compiler to use.
+     * Returns the compiler to be used. Only takes effect if {@code useAnt} is {@code true}.
+     *
+     * @deprecated use {@code CompileOptions.forkOptions.executable} instead
      */
     @Deprecated
     @Input @Optional
-    private String compiler;
-
     public String getCompiler() {
         return compiler;
     }
 
-    void setCompiler(String compiler) {
+    /**
+     * Sets the compiler to be used. Only takes effect if {@code useAnt} is {@code true}.
+     *
+     * @deprecated use {@code CompileOptions.forkOptions.executable instead}
+     */
+    @Deprecated
+    public void setCompiler(String compiler) {
         DeprecationLogger.nagUserOfDiscontinuedProperty("CompileOptions.compiler", "To use an alternative compiler, "
                 + "set 'CompileOptions.fork' to 'true', and 'CompileOptions.forkOptions.executable' to the path of the compiler executable.");
         this.compiler = compiler;
     }
 
-    @Input
-    private boolean includeJavaRuntime;
-
+    /**
+     * Tells whether the Java runtime should be put on the compile class path. Only takes effect if
+     * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     */
     public boolean isIncludeJavaRuntime() {
         return includeJavaRuntime;
     }
 
+    /**
+     * Tells whether the Java runtime should be put on the compile class path. Only takes effect if
+     * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     */
     // @Input not recognized if there is only an "is" method
+    @Input
     public boolean getIncludeJavaRuntime() {
         return includeJavaRuntime;
     }
 
+    /**
+     * Sets whether the Java runtime should be put on the compile class path. Only takes effect if
+     * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     */
     public void setIncludeJavaRuntime(boolean includeJavaRuntime) {
         this.includeJavaRuntime = includeJavaRuntime;
     }
 
     /**
-     * The bootstrap classpath to use when compiling.
+     * Returns the bootstrap classpath to be used for the compiler process.
+     * Only takes effect if {@code fork} is {@code true}. Defaults to {@code null}.
      */
-    @Input @Optional
-    private String bootClasspath;
-
+    @Input
+    @Optional
     public String getBootClasspath() {
         return bootClasspath;
     }
 
+    /**
+     * Sets the bootstrap classpath to be used for the compiler process.
+     * Only takes effect if {@code fork} is {@code true}. Defaults to {@code null}.
+     */
     public void setBootClasspath(String bootClasspath) {
         this.bootClasspath = bootClasspath;
     }
 
     /**
-     * The extension dirs to use when compiling.
+     * Returns the extension dirs to be used for the compiler process.
+     * Only takes effect if {@code fork} is {@code true}. Defaults to {@code null}.
      */
-    @Input @Optional
-    private String extensionDirs;
-
+    @Input 
+    @Optional
     public String getExtensionDirs() {
         return extensionDirs;
     }
 
+    /**
+     * Sets the extension dirs to be used for the compiler process.
+     * Only takes effect if {@code fork} is {@code true}. Defaults to {@code null}.
+     */
     public void setExtensionDirs(String extensionDirs) {
         this.extensionDirs = extensionDirs;
     }
 
     /**
-     * The arguments to pass to the compiler.
+     * Returns any additional arguments to be passed to the compiler.
+     * Defaults to the empty list.
      */
     @Input
-    private List<String> compilerArgs = Lists.newArrayList();
-
     public List<String> getCompilerArgs() {
         return compilerArgs;
     }
 
+    /**
+     * Sets any additional arguments to be passed to the compiler.
+     * Defaults to the empty list.
+     */
     public void setCompilerArgs(List<String> compilerArgs) {
         this.compilerArgs = compilerArgs;
     }
 
     /**
-     * Whether to use the Ant javac task or Gradle's own Java compiler integration.
-     * Defaults to <tt>false</tt>.
+     * Tells whether to use the Ant javac task over Gradle's own Java compiler integration.
+     * Defaults to {@code false}.
      */
-    private boolean useAnt;
-
     public boolean isUseAnt() {
         return useAnt;
     }
 
+    /**
+     * Sets whether to use the Ant javac task over Gradle's own Java compiler integration.
+     * Defaults to {@code false}.
+     */
     public void setUseAnt(boolean useAnt) {
         this.useAnt = useAnt;
     }
 
     /**
-     * Convenience method to set fork options with named parameter syntax.
+     * Convenience method to set {@link ForkOptions} with named parameter syntax.
+     * Calling this method will set {@code fork} to {@code true}.
      */
-    CompileOptions fork(Map<String, Object> forkArgs) {
+    public CompileOptions fork(Map<String, Object> forkArgs) {
         fork = true;
         forkOptions.define(forkArgs);
         return this;
     }
 
     /**
-     * Convenience method to set debug options with named parameter syntax.
+     * Convenience method to set {@link DebugOptions} with named parameter syntax.
+     * Calling this method will set {@code debug} to {@code true}.
      */
-    CompileOptions debug(Map<String, Object> debugArgs) {
+    public CompileOptions debug(Map<String, Object> debugArgs) {
         debug = true;
         debugOptions.define(debugArgs);
         return this;
     }
 
     /**
-     * Set the dependency options from a map.  See  {@link DependOptions}  for
-     * a list of valid properties.  Calling this method will enable use
-     * of the depend task during a compile.
+     * Convenience method to set {@link DependOptions} with named parameter syntax.
+     * Calling this method will set {@code useDepend} to {@code true}.
      */
-    CompileOptions depend(Map<String, Object> dependArgs) {
+    public CompileOptions depend(Map<String, Object> dependArgs) {
         useDepend = true;
         dependOptions.define(dependArgs);
         return this;
     }
 
+    /**
+     * Internal method.
+     */
     protected List<String> excludedFieldsFromOptionMap() {
         return Arrays.asList("debugOptions", "forkOptions", "compilerArgs", "dependOptions", "useDepend", "useAnt");
     }
 
+    /**
+     * Internal method.
+     */
     protected Map<String, String> fieldName2AntMap() {
         return ImmutableMap.of("warnings", "nowarn", "bootClasspath", "bootclasspath", "extensionDirs", "extdirs", "failOnError", "failonerror", "listFiles", "listfiles");
     }
 
+    /**
+     * Internal method.
+     */
     protected Map<String, ? extends Callable<Object>> fieldValue2AntMap() {
         return ImmutableMap.of("warnings", new Callable<Object>() {
             public Object call() {
@@ -362,6 +476,9 @@ public class CompileOptions extends AbstractOptions {
         });
     }
 
+    /**
+     * Internal method.
+     */
     public Map<String, Object> optionMap() {
         Map<String, Object> map = super.optionMap();
         map.putAll(debugOptions.optionMap());
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.java
index 37fdece..98c0dc0 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.java
@@ -23,15 +23,18 @@ import org.gradle.api.tasks.Optional;
 import java.util.Map;
 
 /**
- * Debug options for Java compilation.
+ * Debug options for Java compilation. Only take effect if {@link CompileOptions#debug}
+ * is set to {@code true}.
  *
  * @author Hans Dockter
  */
 public class DebugOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
 
+    private String debugLevel;
+
     /**
-     * Tells which debugging information will be generated. The value is a comma-separated
+     * Tells which debugging information is to be generated. The value is a comma-separated
      * list of any of the following keywords (without spaces in between):
      *
      * <dl>
@@ -44,20 +47,23 @@ public class DebugOptions extends AbstractOptions {
      * </dl>
      *
      * By default, only source and line debugging information will be generated.
-     *
-     * <p>This option only takes effect if {@link CompileOptions#debug} is set to {@code true}.
      */
-    @Input @Optional
-    private String debugLevel;
-
+    @Input
+    @Optional
     public String getDebugLevel() {
         return debugLevel;
     }
 
+    /**
+     * Sets which debug information is to be generated.
+     */
     public void setDebugLevel(String debugLevel) {
         this.debugLevel = debugLevel;
     }
 
+    /**
+     * Internal method.
+     */
     protected Map<String, String> fieldName2AntMap() {
         return ImmutableMap.of("debugLevel", "debuglevel");
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.java
index 57face3..93bc1df 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.java
@@ -20,26 +20,17 @@ import com.google.common.collect.ImmutableList;
 import java.util.List;
 
 /**
- * <p>Options to send to Ant's depend task. Depends will delete out of date class files before compiling.
- * This is not fool-proof, but will cut down on the frequency of having to do a clean build. This may or may
- * not be faster than a clean build.
- * See the <a href="http://ant.apache.org/manual/OptionalTasks/depend.html" target="_blank">Ant Reference</a>
- * for more information.
+ * Options for the Ant Depend task. Only take effect if {@code CompileOptions.useAnt} and
+ * {@code CompileOptions.useDepend} are {@code true}.
  *
- * <h2>Ant Options</h2>
- * <ul>
- *      <li>srcDir  - <b>IGNORED</b> - set automatically</li>
- *      <li>destDir - <b>IGNORED</b> - set automatically</li>
- *      <li>cache - <b>IGNORED</b> - set automatically</li>
- *      <li>closure - boolean controlling depth of dependency graph traversal</li>
- *      <li>dump - dump dependency information to log</li>
- *      <li>classpath - extra classes to check</li>
- *      <li>warnOnRmiStubs - disables warnings for rmi stubs with no source</li>
- * </ul>
+ * <p>The Ant Depend task will delete out-of-date and dependent class files before compiling
+ * so that only those files will be recompiled. This is not fool-proof but may result in faster compilation.
+ * See the <a href="http://ant.apache.org/manual/Tasks/depend.html" target="_blank">Ant Reference</a>
+ * for more information.
  *
- * <p>
- * There is an additional <tt>useCache</tt> boolean option to enable/disable caching of dependency information. It is true
- * by default.
+ * <p>The {@code srcDir}, {@code destDir}, and {@code cache} properties of the Ant task
+ * are set automatically. The latter is replaced by a {@code useCache} option to enable/disable caching of
+ * dependency information.
  *
  * @author Steve Appling
  */
@@ -48,55 +39,92 @@ public class DependOptions extends AbstractOptions {
 
     private boolean useCache = true;
 
+    private boolean closure;
+
+    private boolean dump;
+
+    private String classpath = "";
+
+    private boolean warnOnRmiStubs = true;
+
+    /**
+     * Tells whether to cache dependency information. Defaults to {@code true}.
+     */
     public boolean isUseCache() {
         return useCache;
     }
 
+    /**
+     * Sets whether to cache dependency information. Defaults to {@code true}.
+     */
     public void setUseCache(boolean useCache) {
         this.useCache = useCache;
     }
 
-    private boolean closure;
-
+    /**
+     * Tells whether to delete the transitive closure of outdated files or only their
+     * direct dependencies. Defaults to {@code false}.
+     */
     public boolean isClosure() {
         return closure;
     }
 
+    /**
+     * Sets whether to delete the transitive closure of outdated files or only their
+     * direct dependencies. Defaults to {@code false}.
+     */
     public void setClosure(boolean closure) {
         this.closure = closure;
     }
 
-    private boolean dump;
-
+    /**
+     * Tells whether to log dependency information. Defaults to {@code false}.
+     */
     public boolean isDump() {
         return dump;
     }
 
+    /**
+     * Sets whether to log dependency information. Defaults to {@code false}.
+     */
     public void setDump(boolean dump) {
         this.dump = dump;
     }
 
-    private String classpath = "";
-
+    /**
+     * Returns the compile classpath for which dependencies should also be checked.
+     * Defaults to the empty string.
+     */
     public String getClasspath() {
         return classpath;
     }
 
+    /**
+     * Sets the compile classpath for which dependencies should also be checked.
+     * Defaults to the empty string.
+     */
     public void setClasspath(String classpath) {
         this.classpath = classpath;
     }
 
-    private boolean warnOnRmiStubs = true;
-
+    /**
+     * Tells whether to warn on RMI stubs without source. Defaults to {@code true}.
+     */
     public boolean isWarnOnRmiStubs() {
         return warnOnRmiStubs;
     }
 
+    /**
+     * Sets whether to warn on RMI stubs without source. Defaults to {@code true}.
+     */
     public void setWarnOnRmiStubs(boolean warnOnRmiStubs) {
         this.warnOnRmiStubs = warnOnRmiStubs;
     }
 
-    public List<String> excludedFieldsFromOptionMap() {
+    /**
+     * Internal method.
+     */
+    protected List<String> excludedFieldsFromOptionMap() {
         return ImmutableList.of("srcDir", "destDir", "cache", "useCache");
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.java
index d0d4d18..70ec809 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.java
@@ -27,86 +27,122 @@ import java.util.List;
 import java.util.Map;
 
 /**
- * Fork options for Java compilation.
+ * Fork options for Java compilation. Only take effect if {@code CompileOptions.fork} is {@code true}.
  *
  * @author Hans Dockter
  */
 public class ForkOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
 
-    /**
-     * The executable to use to fork the compiler.
-     */
-    @Input @Optional
     private String executable;
 
+    private String memoryInitialSize;
+
+    private String memoryMaximumSize;
+
+    private String tempDir;
+
+    private List<String> jvmArgs = Lists.newArrayList();
+
+    /**
+     * Returns the compiler executable to be used. If set,
+     * a new compiler process will be forked for every compile task.
+     * Defaults to {@code null}.
+     */
+    @Input
+    @Optional
     public String getExecutable() {
         return executable;
     }
 
+    /**
+     * Sets the compiler executable to be used. If set,
+     * a new compiler process will be forked for every compile task.
+     * Defaults to {@code null}.
+     */
     public void setExecutable(String executable) {
         this.executable = executable;
     }
 
     /**
-     * The initial heap size for the compiler process.
+     * Returns the initial heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
      */
-    private String memoryInitialSize;
-
     public String getMemoryInitialSize() {
         return memoryInitialSize;
     }
 
+    /**
+     * Sets the initial heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
     public void setMemoryInitialSize(String memoryInitialSize) {
         this.memoryInitialSize = memoryInitialSize;
     }
 
     /**
-     * The maximum heap size for the compiler process.
+     * Returns the maximum heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
      */
-    private String memoryMaximumSize;
-
     public String getMemoryMaximumSize() {
         return memoryMaximumSize;
     }
 
+    /**
+     * Sets the maximum heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
     public void setMemoryMaximumSize(String memoryMaximumSize) {
         this.memoryMaximumSize = memoryMaximumSize;
     }
 
     /**
-   * Directory for temporary files. Only used if compilation is done by an
-   * underlying Ant javac task, happens in a forked process, and the command
-   * line args length exceeds 4k. Defaults to <tt>java.io.tmpdir</tt>.
-   */
-    private String tempDir;
-
+     * Returns the directory used for temporary files that may be created to pass
+     * command line arguments to the compiler process. Defaults to {@code null},
+     * in which case the directory will be chosen automatically.
+     */
     public String getTempDir() {
         return tempDir;
     }
 
+    /**
+     * Sets the directory used for temporary files that may be created to pass
+     * command line arguments to the compiler process. Defaults to {@code null},
+     * in which case the directory will be chosen automatically.
+     */
     public void setTempDir(String tempDir) {
         this.tempDir = tempDir;
     }
 
     /**
-     * Any additional JVM arguments for the compiler process.
+     * Returns any additional JVM arguments for the compiler process.
+     * Defaults to the empty list.
      */
-    private List<String> jvmArgs = Lists.newArrayList();
-
+    @Input
+    @Optional
     public List<String> getJvmArgs() {
         return jvmArgs;
     }
 
+    /**
+     * Sets any additional JVM arguments for the compiler process.
+     * Defaults to the empty list.
+     */
     public void setJvmArgs(List<String> jvmArgs) {
         this.jvmArgs = jvmArgs;
     }
 
-    public Map<String, String> fieldName2AntMap() {
+    /**
+     * Internal method.
+     */
+    protected Map<String, String> fieldName2AntMap() {
         return ImmutableMap.of("tempDir", "tempdir");
     }
 
-    public List<String> excludedFieldsFromOptionMap() {
+    /**
+     * Internal method.
+     */
+    protected List<String> excludedFieldsFromOptionMap() {
         return ImmutableList.of("jvmArgs");
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.java
index f2d4370..52db233 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.java
@@ -17,6 +17,9 @@ package org.gradle.api.tasks.compile;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+import org.gradle.api.Experimental;
 import org.gradle.api.tasks.Input;
 
 import java.io.File;
@@ -31,148 +34,252 @@ import java.util.Map;
 public class GroovyCompileOptions extends AbstractOptions {
     private static final long serialVersionUID = 0;
 
-    /**
-     * Tells whether the compilation task should fail if compile errors occurred. Defaults to <tt>true</tt>.
-     */
     private boolean failOnError = true;
 
+    private boolean verbose;
+
+    private boolean listFiles;
+
+    private String encoding = "UTF-8";
+
+    private boolean fork = true;
+
+    private boolean keepStubs;
+
+    private List<String> fileExtensions = ImmutableList.of("java", "groovy");
+
+    private GroovyForkOptions forkOptions = new GroovyForkOptions();
+
+    private Map<String, Boolean> optimizationOptions = Maps.newHashMap();
+
+    private boolean stacktrace;
+
+    private boolean useAnt;
+
+    private boolean includeJavaRuntime;
+
+    private File stubDir;
+
+    /**
+     * Tells whether the compilation task should fail if compile errors occurred. Defaults to {@code true}.
+     */
     public boolean isFailOnError() {
         return failOnError;
     }
 
+    /**
+     * Sets whether the compilation task should fail if compile errors occurred. Defaults to {@code true}.
+     */
     public void setFailOnError(boolean failOnError) {
         this.failOnError = failOnError;
     }
 
     /**
-     * Tells whether to turn on verbose output. Defaults to <tt>false</tt>.
+     * Tells whether to turn on verbose output. Defaults to {@code false}.
      */
-    private boolean verbose;
-
     public boolean isVerbose() {
         return verbose;
     }
 
+    /**
+     * Sets whether to turn on verbose output. Defaults to {@code false}.
+     */
     public void setVerbose(boolean verbose) {
         this.verbose = verbose;
     }
 
     /**
-     * Tells whether to print which source files are to be compiled. Defaults to <tt>false</tt>.
+     * Tells whether to print which source files are to be compiled. Defaults to {@code false}.
      */
-    private boolean listFiles;
-
     public boolean isListFiles() {
         return listFiles;
     }
 
+    /**
+     * Sets whether to print which source files are to be compiled. Defaults to {@code false}.
+     */
     public void setListFiles(boolean listFiles) {
         this.listFiles = listFiles;
     }
 
     /**
-     * The source encoding. Defaults to <tt>UTF-8</tt>.
+     * Tells the source encoding. Defaults to {@code UTF-8}.
      */
     @Input
-    private String encoding = "UTF-8";
-
     public String getEncoding() {
         return encoding;
     }
 
+    /**
+     * Sets the source encoding. Defaults to {@code UTF-8}.
+     */
     public void setEncoding(String encoding) {
         this.encoding = encoding;
     }
 
     /**
-     * Tells whether to run the Groovy compiler in a separate process. Defaults to <tt>true</tt>.
+     * Tells whether to run the Groovy compiler in a separate process. Defaults to {@code true}.
      */
-    private boolean fork = true;
-
     public boolean isFork() {
         return fork;
     }
 
+    /**
+     * Sets whether to run the Groovy compiler in a separate process. Defaults to {@code true}.
+     */
     public void setFork(boolean fork) {
         this.fork = fork;
     }
 
     /**
-     * Options for running the Groovy compiler in a separate process. These options only take effect
-     * if <tt>fork</tt> is set to <tt>true</tt>.
+     * Returns options for running the Groovy compiler in a separate process. These options only take effect
+     * if {@code fork} is set to {@code true}.
      */
-    private GroovyForkOptions forkOptions = new GroovyForkOptions();
-
     public GroovyForkOptions getForkOptions() {
         return forkOptions;
     }
 
+    /**
+     * Sets options for running the Groovy compiler in a separate process. These options only take effect
+     * if {@code fork} is set to {@code true}.
+     */
     public void setForkOptions(GroovyForkOptions forkOptions) {
         this.forkOptions = forkOptions;
     }
 
     /**
-     * Tells whether the Java runtime should be put on the compiler's compile class path. Defaults to <tt>false</tt>.
+     * Returns optimization options for the Groovy compiler. Allowed values for an option are {@code true} and {@code false}.
+     * Only takes effect when compiling against Groovy 1.8 or higher.
+     *
+     * <p>Known options are:
+     *
+     * <dl>
+     *     <dt>indy
+     *     <dd>Use the invokedynamic bytecode instruction. Requires JDK7 or higher and Groovy 2.0 or higher. Disabled by default.
+     *     <dt>int
+     *     <dd>Optimize operations on primitive types (e.g. integers). Enabled by default.
+     *     <dt>all
+     *     <dd>Enable or disable all optimizations. Note that some optimizations might be mutually exclusive.
+     * </dl>
      */
-    @Input
-    private boolean includeJavaRuntime;
-
-    public boolean isIncludeJavaRuntime() {
-        return includeJavaRuntime;
+    public Map<String, Boolean> getOptimizationOptions() {
+        return optimizationOptions;
     }
 
-    public void setIncludeJavaRuntime(boolean includeJavaRuntime) {
-        this.includeJavaRuntime = includeJavaRuntime;
+    /**
+     * Sets optimization options for the Groovy compiler. Allowed values for an option are {@code true} and {@code false}.
+     * Only takes effect when compiling against Groovy 1.8 or higher.
+     */
+    public void setOptimizationOptions(Map<String, Boolean> optimizationOptions) {
+        this.optimizationOptions = optimizationOptions;
     }
 
     /**
      * Tells whether to print a stack trace when the compiler hits a problem (like a compile error).
-     * Defaults to <tt>false</tt>.
+     * Defaults to {@code false}.
      */
-    private boolean stacktrace;
-
     public boolean isStacktrace() {
         return stacktrace;
     }
 
+    /**
+     * Sets whether to print a stack trace when the compiler hits a problem (like a compile error).
+     * Defaults to {@code false}.
+     */
     public void setStacktrace(boolean stacktrace) {
         this.stacktrace = stacktrace;
     }
 
-    private boolean useAnt;
-
+    /**
+     * Tells whether the groovyc Ant task should be used over Gradle's own Groovy compiler integration.
+     * Defaults to {@code false}.
+     */
+    @Input
     public boolean isUseAnt() {
         return useAnt;
     }
 
+    /**
+     * Sets whether the groovyc Ant task should be used over Gradle's own Groovy compiler integration.
+     * Defaults to {@code false}.
+     */
     public void setUseAnt(boolean useAnt) {
         this.useAnt = useAnt;
     }
 
-    private File stubDir;
+    /**
+     * Tells whether the Java runtime should be put on the compile class path. Only takes effect if
+     * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     */
+    @Input
+    public boolean isIncludeJavaRuntime() {
+        return includeJavaRuntime;
+    }
+
+    /**
+     * Sets whether the Java runtime should be put on the compile class path. Only takes effect if
+     * {@code useAnt} is {@code true}. Defaults to {@code false}.
+     */
+    public void setIncludeJavaRuntime(boolean includeJavaRuntime) {
+        this.includeJavaRuntime = includeJavaRuntime;
+    }
 
+    /**
+     * Returns the directory where Java stubs for Groovy classes will be stored during Java/Groovy joint
+     * compilation. Defaults to {@code null}, in which case a temporary directory will be used.
+     */
     public File getStubDir() {
         return stubDir;
     }
 
+    /**
+     * Sets the directory where Java stubs for Groovy classes will be stored during Java/Groovy joint
+     * compilation. Defaults to {@code null}, in which case a temporary directory will be used.
+     */
     public void setStubDir(File stubDir) {
         this.stubDir = stubDir;
     }
 
-    private boolean keepStubs;
+    /**
+     * Returns the list of acceptable source file extensions. Only takes effect when compiling against
+     * Groovy 1.7 or higher. Defaults to {@code ImmutableList.of("java", "groovy")}.
+     */
+    @Input
+    @Experimental
+    public List<String> getFileExtensions() {
+        return fileExtensions;
+    }
 
+    /**
+     * Sets the list of acceptable source file extensions. Only takes effect when compiling against
+     * Groovy 1.7 or higher. Defaults to {@code ImmutableList.of("java", "groovy")}.
+     */
+    @Experimental
+    public void setFileExtensions(List<String> fileExtensions) {
+        this.fileExtensions = fileExtensions;
+    }
+
+    /**
+     * Tells whether Java stubs for Groovy classes generated during Java/Groovy joint compilation
+     * should be kept after compilation has completed. Useful for joint compilation debugging purposes.
+     * Defaults to {@code false}.
+     */
     public boolean isKeepStubs() {
         return keepStubs;
     }
 
+    /**
+     * Sets whether Java stubs for Groovy classes generated during Java/Groovy joint compilation
+     * should be kept after compilation has completed. Useful for joint compilation debugging purposes.
+     * Defaults to {@code false}.
+     */
     public void setKeepStubs(boolean keepStubs) {
         this.keepStubs = keepStubs;
     }
 
     /**
-     * Shortcut for setting both <tt>fork</tt> and <tt>forkOptions</tt>.
-     *
-     * @param forkArgs fork options in map notation
+     * Convenience method to set {@link ForkOptions} with named parameter syntax.
+     * Calling this method will set {@code fork} to {@code true}.
      */
     public GroovyCompileOptions fork(Map forkArgs) {
         fork = true;
@@ -180,17 +287,29 @@ public class GroovyCompileOptions extends AbstractOptions {
         return this;
     }
 
-    public List<String> excludedFieldsFromOptionMap() {
-        return ImmutableList.of("forkOptions", "useAnt", "stubDir", "keepStubs");
+    /**
+     * Internal method.
+     */
+    protected List<String> excludedFieldsFromOptionMap() {
+        return ImmutableList.of("forkOptions", "optimizationOptions", "useAnt", "stubDir", "keepStubs", "fileExtensions");
     }
 
-    public Map<String, String> fieldName2AntMap() {
+    /**
+     * Internal method.
+     */
+    protected Map<String, String> fieldName2AntMap() {
         return ImmutableMap.of("failOnError", "failonerror", "listFiles", "listfiles");
     }
 
+    /**
+     * Internal method.
+     */
     public Map<String, Object> optionMap() {
         Map<String, Object> map = super.optionMap();
         map.putAll(forkOptions.optionMap());
+        if (optimizationOptions.containsKey("indy")) {
+            map.put("indy", optimizationOptions.get("indy"));
+        }
         return map;
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.java
index 535e958..f5b78e5 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.java
@@ -17,11 +17,14 @@ package org.gradle.api.tasks.compile;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Optional;
 
 import java.util.List;
 
 /**
- * Fork options for Groovy compilation.
+ * Fork options for Groovy compilation. Only take effect if {@code GroovyCompileOptions.fork}
+ * is {@code true}.
  *
  * @author Hans Dockter
  */
@@ -32,36 +35,62 @@ public class GroovyForkOptions extends AbstractOptions {
 
     private String memoryMaximumSize;
 
-    /**
-     * Any additional JVM arguments for the compiler process.
-     */
     private List<String> jvmArgs = Lists.newArrayList();
 
+    /**
+     * Returns the initial heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
     public String getMemoryInitialSize() {
         return memoryInitialSize;
     }
 
+    /**
+     * Sets the initial heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
     public void setMemoryInitialSize(String memoryInitialSize) {
         this.memoryInitialSize = memoryInitialSize;
     }
 
+    /**
+     * Returns the maximum heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
     public String getMemoryMaximumSize() {
         return memoryMaximumSize;
     }
 
+    /**
+     * Sets the maximum heap size for the compiler process.
+     * Defaults to {@code null}, in which case the JVM's default will be used.
+     */
     public void setMemoryMaximumSize(String memoryMaximumSize) {
         this.memoryMaximumSize = memoryMaximumSize;
     }
 
+    /**
+     * Returns any additional JVM arguments for the compiler process.
+     * Defaults to the empty list.
+     */
+    @Input
+    @Optional
     public List<String> getJvmArgs() {
         return jvmArgs;
     }
 
+    /**
+     * Sets any additional JVM arguments for the compiler process.
+     * Defaults to the empty list.
+     */
     public void setJvmArgs(List<String> jvmArgs) {
         this.jvmArgs = jvmArgs;
     }
 
-    public List<String> excludedFieldsFromOptionMap() {
+    /**
+     * Internal method.
+     */
+    protected List<String> excludedFieldsFromOptionMap() {
         return ImmutableList.of("jvmArgs");
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
index 9c1b8b8..9e60699 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
@@ -28,26 +28,28 @@ import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.internal.tasks.testing.detection.DefaultTestExecuter;
 import org.gradle.api.internal.tasks.testing.detection.TestExecuter;
 import org.gradle.api.internal.tasks.testing.junit.JUnitTestFramework;
-import org.gradle.api.internal.tasks.testing.logging.DefaultTestLogging;
-import org.gradle.api.internal.tasks.testing.logging.StandardStreamsLogger;
-import org.gradle.api.internal.tasks.testing.results.TestListenerAdapter;
-import org.gradle.api.internal.tasks.testing.results.TestLogger;
-import org.gradle.api.internal.tasks.testing.results.TestSummaryListener;
+import org.gradle.api.internal.tasks.testing.logging.*;
+import org.gradle.api.internal.tasks.testing.results.*;
 import org.gradle.api.internal.tasks.testing.testng.TestNGTestFramework;
+import org.gradle.api.logging.LogLevel;
 import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.testing.logging.TestLogging;
+import org.gradle.api.tasks.testing.logging.TestLoggingContainer;
 import org.gradle.api.tasks.util.PatternFilterable;
 import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.internal.reflect.Instantiator;
 import org.gradle.listener.ListenerBroadcast;
 import org.gradle.listener.ListenerManager;
+import org.gradle.logging.ConsoleRenderer;
 import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.logging.StyledTextOutputFactory;
 import org.gradle.messaging.actor.ActorFactory;
 import org.gradle.process.JavaForkOptions;
 import org.gradle.process.ProcessForkOptions;
 import org.gradle.process.internal.DefaultJavaForkOptions;
 import org.gradle.process.internal.WorkerProcessBuilder;
 import org.gradle.util.ConfigureUtil;
-import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -60,7 +62,7 @@ import java.util.Set;
  * <p>
  * An example with a blend of various settings
  * <pre autoTested=''>
- * apply plugin: 'java' //so that 'test' task is added
+ * apply plugin: 'java' // adds 'test' task
  *
  * test {
  *   //configuring a system property for tests
@@ -89,8 +91,13 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public class Test extends ConventionTask implements JavaForkOptions, PatternFilterable, VerificationTask {
-    private TestExecuter testExecuter;
+    private final ListenerBroadcast<TestListener> testListenerBroadcaster;
+    private final ListenerBroadcast<TestOutputListener> testOutputListenerBroadcaster;
+    private final StyledTextOutputFactory textOutputFactory;
+    private final TestLoggingContainer testLogging;
     private final DefaultJavaForkOptions options;
+
+    private TestExecuter testExecuter;
     private List<File> testSrcDirs = new ArrayList<File>();
     private File testClassesDir;
     private File testResultsDir;
@@ -103,18 +110,17 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     private boolean scanForTestClasses = true;
     private long forkEvery;
     private int maxParallelForks = 1;
-    private ListenerBroadcast<TestListener> testListenerBroadcaster;
-    private final ListenerBroadcast<TestOutputListener> testOutputListenerBroadcaster;
-    private final TestLogging testLogging = new DefaultTestLogging();
 
     public Test() {
-        testListenerBroadcaster = getServices().get(ListenerManager.class).createAnonymousBroadcaster(
-                TestListener.class);
+        testListenerBroadcaster = getServices().get(ListenerManager.class).createAnonymousBroadcaster(TestListener.class);
         testOutputListenerBroadcaster = getServices().get(ListenerManager.class).createAnonymousBroadcaster(TestOutputListener.class);
-        this.testExecuter = new DefaultTestExecuter(getServices().getFactory(WorkerProcessBuilder.class), getServices().get(
-                ActorFactory.class));
+        textOutputFactory = getServices().get(StyledTextOutputFactory.class);
         options = new DefaultJavaForkOptions(getServices().get(FileResolver.class));
         options.setEnableAssertions(true);
+        testExecuter = new DefaultTestExecuter(getServices().getFactory(WorkerProcessBuilder.class), getServices().get(ActorFactory.class));
+
+        Instantiator instantiator = getServices().get(Instantiator.class);
+        testLogging = instantiator.newInstance(DefaultTestLoggingContainer.class, instantiator);
     }
 
     void setTestExecuter(TestExecuter testExecuter) {
@@ -385,19 +391,26 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
 
     @TaskAction
     public void executeTests() {
-        TestSummaryListener listener = new TestSummaryListener(LoggerFactory.getLogger(Test.class));
-        addTestListener(listener);
-        addTestListener(new TestLogger(getServices().get(ProgressLoggerFactory.class)));
-        addTestOutputListener(new StandardStreamsLogger(LoggerFactory.getLogger(Test.class), testLogging));
+        LogLevel currentLevel = getCurrentLogLevel();
+        TestLogging levelLogging = testLogging.get(currentLevel);
+        TestExceptionFormatter exceptionFormatter = getExceptionFormatter(levelLogging);
+        TestEventLogger eventLogger = new TestEventLogger(textOutputFactory, currentLevel, levelLogging, exceptionFormatter);
+        addTestListener(eventLogger);
+        addTestOutputListener(eventLogger);
+
+        ProgressLoggerFactory progressLoggerFactory = getServices().get(ProgressLoggerFactory.class);
+        TestCountLogger testCountLogger = new TestCountLogger(progressLoggerFactory);
+        addTestListener(testCountLogger);
 
         TestResultProcessor resultProcessor = new TestListenerAdapter(
                 getTestListenerBroadcaster().getSource(), testOutputListenerBroadcaster.getSource());
-        testExecuter.execute(this, resultProcessor);
 
+        testExecuter.execute(this, resultProcessor);
         testFramework.report();
+        testFramework = null;
 
-        if (!getIgnoreFailures() && listener.hadFailures()) {
-            throw new GradleException("There were failing tests. See the report at " + getTestReportDir() + ".");
+        if (testCountLogger.hadFailures()) {
+            handleTestFailures();
         }
     }
 
@@ -471,7 +484,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
 
     /**
      * <p>Adds a closure to be notified after a test suite has executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
-     * and {@link org.gradle.api.tasks.testing.TestResult} instance are passed to the closure as a parameter.</p>
+     * and {@link TestResult} instance are passed to the closure as a parameter.</p>
      *
      * <p>This method is also called after all test suites are executed. The provided descriptor will have a null parent
      * suite.</p>
@@ -494,7 +507,7 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
 
     /**
      * Adds a closure to be notified after a test has executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
-     * and {@link org.gradle.api.tasks.testing.TestResult} instance are passed to the closure as a parameter.
+     * and {@link TestResult} instance are passed to the closure as a parameter.
      *
      * @param closure The closure to call.
      */
@@ -904,17 +917,22 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     }
 
     /**
-     * Allows configuring the logging of the test execution, for example log eagerly the standard output, etc.
+     * Allows to set options related to which test events are logged to the console, and on which detail
+     * level. For example, to show more information about exceptions use:
+     *
      * <pre autoTested=''>
      * apply plugin: 'java'
      *
-     * //makes the standard streams (err and out) visible at console when running tests
-     * test.testLogging.showStandardStreams = true
+     * test.testLogging {
+     *     exceptionFormat "full"
+     * }
      * </pre>
      *
-     * @return test logging configuration
+     * For further information see {@link TestLoggingContainer}.
+     *
+     * @return this
      */
-    public TestLogging getTestLogging() {
+    public TestLoggingContainer getTestLogging() {
         return testLogging;
     }
 
@@ -933,4 +951,35 @@ public class Test extends ConventionTask implements JavaForkOptions, PatternFilt
     public void testLogging(Closure closure) {
         ConfigureUtil.configure(closure, testLogging);
     }
+
+    // only way I know of to determine current log level
+    private LogLevel getCurrentLogLevel() {
+        for (LogLevel level : LogLevel.values()) {
+            if (getLogger().isEnabled(level)) {
+                return level;
+            }
+        }
+        throw new AssertionError("could not determine current log level");
+    }
+
+    private TestExceptionFormatter getExceptionFormatter(TestLogging testLogging) {
+        switch (testLogging.getExceptionFormat()) {
+            case SHORT:
+                return new ShortExceptionFormatter(testLogging);
+            case FULL:
+                return new FullExceptionFormatter(testLogging);
+            default:
+                throw new AssertionError();
+        }
+    }
+
+    private void handleTestFailures() {
+        String reportUrl = new ConsoleRenderer().asClickableFileUrl(new File(getTestReportDir(), "index.html"));
+        String message = "There were failing tests. See the report at: " + reportUrl;
+        if (getIgnoreFailures()) {
+            getLogger().warn(message);
+        } else {
+            throw new GradleException(message);
+        }
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestLogging.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestLogging.java
index 46f19cc..8751b17 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestLogging.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/TestLogging.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2012 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.
@@ -17,10 +17,12 @@
 package org.gradle.api.tasks.testing;
 
 /**
- * Configures logging of the test execution, e.g. whether the std err / out should be eagerly shown
+ * Configures logging of the test execution, e.g. whether the std err / out should be eagerly shown.
+ *
+ * @deprecated use {@link org.gradle.api.tasks.testing.logging.TestLogging} instead
  */
+ at Deprecated
 public interface TestLogging {
-
     /**
      * Whether to show eagerly the standard stream events. Standard output is printed at INFO level, standard error at ERROR level.
      *
@@ -33,5 +35,4 @@ public interface TestLogging {
      * Whether to show eagerly the standard stream events. Standard output is printed at INFO level, standard error at ERROR level.
      */
     boolean getShowStandardStreams();
-
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestExceptionFormat.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestExceptionFormat.java
new file mode 100644
index 0000000..7323dbf
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestExceptionFormat.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks.testing.logging;
+
+import org.gradle.api.Experimental;
+
+/**
+ * Determines how exceptions are formatted in test logging.
+ */
+ at Experimental
+public enum TestExceptionFormat {
+    /**
+     * Short display of exceptions.
+     */
+    SHORT,
+    /**
+     * Full display of exceptions.
+     */
+    FULL
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogEvent.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogEvent.java
new file mode 100644
index 0000000..013b61d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogEvent.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks.testing.logging;
+
+import org.gradle.api.Experimental;
+
+/**
+ * Test events that can be logged.
+ */
+ at Experimental
+public enum TestLogEvent {
+    /**
+     * A test has started. This event gets fired both for atomic and composite tests.
+     */
+    STARTED,
+
+    /**
+     * A test has completed successfully. This event gets fired both for atomic and composite tests.
+     */
+    PASSED,
+
+    /**
+     * A test has been skipped. This event gets fired both for atomic and composite tests.
+     */
+    SKIPPED,
+
+    /**
+     * A test has failed. This event gets fired both for atomic and composite tests.
+     */
+    FAILED,
+
+    /**
+     * A test has written a message to standard out.
+     */
+    STANDARD_OUT,
+
+    /**
+     * A test has written a message to standard error.
+     */
+    STANDARD_ERROR
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogging.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogging.java
new file mode 100644
index 0000000..a52eb5e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLogging.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks.testing.logging;
+
+import org.gradle.api.Experimental;
+
+import java.util.Set;
+
+/**
+ * Options that determine which test events get logged, and at which detail.
+ */
+public interface TestLogging extends org.gradle.api.tasks.testing.TestLogging {
+    /**
+     * Returns the events to be logged.
+     *
+     * @return the events to be logged
+     */
+    @Experimental
+    Set<TestLogEvent> getEvents();
+
+    /**
+     * Sets the events to be logged.
+     *
+     * @param events the events to be logged
+     */
+    @Experimental
+    void setEvents(Iterable<?> events);
+
+    /**
+     * Sets the events to be logged. Events can be passed as enum values
+     * (e.g. {@link TestLogEvent#FAILED}) or Strings (e.g. "failed").
+     *
+     * @param events the events to be logged
+     */
+    @Experimental
+    void events(Object... events);
+
+    /**
+     * Returns the minimum granularity of the events to be logged. Typically, 0
+     * corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to
+     * the Gradle-generated test suite for a particular test JVM, 2 corresponds to a test class,
+     * and 3 corresponds to a test method. These values will vary if user-defined
+     * suites are executed.
+     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     *
+     * @return the minimum granularity of the events to be logged
+     */
+    @Experimental
+    int getMinGranularity();
+
+    /**
+     * Sets the minimum granularity of the events to be logged. Typically, 0
+     * corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to
+     * the Gradle-generated test suite for a particular test JVM, 2 corresponds to a test class,
+     * and 3 corresponds to a test method. These values will vary if user-defined
+     * suites are executed.
+     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     *
+     * @param granularity the minimum granularity of the events to be logged
+     */
+    @Experimental
+    void setMinGranularity(int granularity);
+
+    /**
+     * Returns the maximum granularity of the events to be logged. Typically, 0
+     * corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to
+     * the Gradle-generated test suite for a particular test JVM, 2 corresponds to a test class,
+     * and 3 corresponds to a test method. These values will vary if user-defined
+     * suites are executed.
+     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     *
+     * @return the maximum granularity of the events to be logged
+     */
+    @Experimental
+    int getMaxGranularity();
+
+    /**
+     * Returns the maximum granularity of the events to be logged. Typically, 0
+     * corresponds to the Gradle-generated test suite for the whole test run, 1 corresponds to
+     * the Gradle-generated test suite for a particular test JVM, 2 corresponds to a test class,
+     * and 3 corresponds to a test method. These values will vary if user-defined
+     * suites are executed.
+     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     *
+     * @param granularity the maximum granularity of the events to be logged
+     */
+    @Experimental
+    void setMaxGranularity(int granularity);
+
+    /**
+     * Returns the display granularity of the events to be logged. For example, if set to 0,
+     * a method-level event will be displayed as "Test Run > Test Worker x > org.SomeClass > org.someMethod".
+     * If set to 2, the same event will be displayed as "org.someClass > org.someMethod".
+     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     *
+     * @return the display granularity of the events to be logged
+     */
+    @Experimental
+    int getDisplayGranularity();
+
+    /**
+     * Sets the display granularity of the events to be logged. For example, if set to 0,
+     * a method-level event will be displayed as "Test Run > Test Worker x > org.SomeClass > org.someMethod".
+     * If set to 2, the same event will be displayed as "org.someClass > org.someMethod".
+     * <p>-1 denotes the highest granularity and corresponds to an atomic test.
+     *
+     * @param granularity the display granularity of the events to be logged
+     */
+    @Experimental
+    void setDisplayGranularity(int granularity);
+
+    /**
+     * Tells whether exceptions that occur during test execution will be logged.
+     * Typically these exceptions coincide with a "failed" event.
+     *
+     * @return whether exceptions that occur during test execution will be logged
+     */
+    @Experimental
+    boolean getShowExceptions();
+
+    /**
+     * Sets whether exceptions that occur during test execution will be logged.
+     *
+     * @param flag whether exceptions that occur during test execution will be logged
+     */
+    @Experimental
+    void setShowExceptions(boolean flag);
+
+    /**
+     * Tells whether causes of exceptions that occur during test execution will be logged.
+     * Only relevant if {@code showExceptions} is {@code true}.
+     *
+     * @return whether causes of exceptions that occur during test execution will be logged
+     */
+    @Experimental
+    boolean getShowCauses();
+
+    /**
+     * Sets whether causes of exceptions that occur during test execution will be logged.
+     * Only relevant if {@code showExceptions} is {@code true}.
+     *
+     * @param flag whether causes of exceptions that occur during test execution will be logged
+     */
+    @Experimental
+    void setShowCauses(boolean flag);
+
+    /**
+     * Tells whether stack traces of exceptions that occur during test execution will be logged.
+     *
+     * @return whether stack traces of exceptions that occur during test execution will be logged
+     */
+    @Experimental
+    boolean getShowStackTraces();
+
+    /**
+     * Sets whether stack traces of exceptions that occur during test execution will be logged.
+     *
+     * @param flag whether stack traces of exceptions that occur during test execution will be logged
+     */
+    @Experimental
+    void setShowStackTraces(boolean flag);
+
+    /**
+     * Returns the format to be used for logging test exceptions. Only relevant if {@code showStackTraces} is {@code true}.
+     *
+     * @return the format to be used for logging test exceptions
+     */
+    @Experimental
+    TestExceptionFormat getExceptionFormat();
+
+    /**
+     * Sets the format to be used for logging test exceptions. Only relevant if {@code showStackTraces} is {@code true}.
+     *
+     * @param exceptionFormat the format to be used for logging test exceptions
+     */
+    @Experimental
+    void setExceptionFormat(Object exceptionFormat);
+
+    /**
+     * Returns the set of filters to be used for sanitizing test stack traces.
+     *
+     * @return the set of filters to be used for sanitizing test stack traces
+     */
+    @Experimental
+    Set<TestStackTraceFilter> getStackTraceFilters();
+
+    /**
+     * Sets the set of filters to be used for sanitizing test stack traces.
+     *
+     * @param stackTraces the set of filters to be used for sanitizing test stack traces
+     */
+    @Experimental
+    void setStackTraceFilters(Iterable<?> stackTraces);
+
+    /**
+     * Convenience method for {@link #setStackTraceFilters(java.lang.Iterable)}. Accepts both enum values and Strings.
+     */
+    @Experimental
+    void stackTraceFilters(Object... stackTraces);
+
+    /**
+     * Tells whether output on standard out and standard error will be logged. Equivalent to checking if both
+     * log events {@link TestLogEvent#STANDARD_OUT} and {@link TestLogEvent#STANDARD_ERROR} are set.
+     */
+     boolean getShowStandardStreams();
+
+    /**
+     * Sets whether output on standard out and standard error will be logged. Equivalent to setting
+     * log events {@link TestLogEvent#STANDARD_OUT} and {@link TestLogEvent#STANDARD_ERROR}.
+     */
+     TestLogging setShowStandardStreams(boolean flag);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLoggingContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLoggingContainer.java
new file mode 100644
index 0000000..66a5921
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestLoggingContainer.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks.testing.logging;
+
+import org.gradle.api.Action;
+import org.gradle.api.Experimental;
+import org.gradle.api.logging.LogLevel;
+
+/**
+ * Container for all test logging related options. Different options
+ * can be set for each log level. Options that are set directly (without
+ * specifying a log level) apply to log level LIFECYCLE. Example:
+ *
+ * <pre autoTested=''>
+ * apply plugin: 'java'
+ *
+ * test {
+ *     testLogging {
+ *         // set options for log level LIFECYCLE
+ *         events "failed"
+ *         exceptionFormat "short"
+ *         // set options for log level DEBUG
+ *         debug {
+ *             events "started", "skipped", "failed"
+ *             exceptionFormat "full"
+ *         }
+ *     }
+ * }
+ * </pre>
+ *
+ * The defaults that are in place show progressively more information
+ * on log levels LIFECYCLE, INFO, and DEBUG, respectively.
+ */
+ at Experimental
+public interface TestLoggingContainer extends TestLogging {
+    /**
+     * Returns logging options for debug level.
+     *
+     * @return logging options for debug level
+     */
+    TestLogging getDebug();
+
+    /**
+     * Sets logging options for debug level.
+     *
+     * @param logging logging options for debug level
+     */
+    void setDebug(TestLogging logging);
+
+    /**
+     * Configures logging options for debug level.
+     *
+     * @param action logging options for debug level
+     */
+    void debug(Action<TestLogging> action);
+
+    /**
+     * Gets logging options for info level.
+     *
+     * @return logging options for info level
+     */
+    TestLogging getInfo();
+
+    /**
+     * Sets logging options for info level.
+     *
+     * @param logging logging options for info level
+     */
+    void setInfo(TestLogging logging);
+
+    /**
+     * Configures logging options for info level.
+     *
+     * @param action logging options for info level
+     */
+    void info(Action<TestLogging> action);
+
+    /**
+     * Returns logging options for lifecycle level.
+     *
+     * @return logging options for lifecycle level
+     */
+    TestLogging getLifecycle();
+
+    /**
+     * Sets logging options for lifecycle level.
+     *
+     * @param logging logging options for lifecycle level
+     */
+    void setLifecycle(TestLogging logging);
+
+    /**
+     * Configures logging options for lifecycle level.
+     *
+     * @param action logging options for lifecycle level
+     */
+    void lifecycle(Action<TestLogging> action);
+
+    /**
+     * Gets logging options for warn level.
+     *
+     * @return logging options for warn level
+     */
+    TestLogging getWarn();
+
+    /**
+     * Sets logging options for warn level.
+     *
+     * @param logging logging options for warn level
+     */
+    void setWarn(TestLogging logging);
+
+    /**
+     * Configures logging options for warn level.
+     *
+     * @param action logging options for warn level
+     */
+    void warn(Action<TestLogging> action);
+
+    /**
+     * Returns logging options for quiet level.
+     *
+     * @return logging options for quiet level
+     */
+    TestLogging getQuiet();
+
+    /**
+     * Sets logging options for quiet level.
+     *
+     * @param logging logging options for quiet level
+     */
+    void setQuiet(TestLogging logging);
+
+    /**
+     * Configures logging options for quiet level.
+     *
+     * @param action logging options for quiet level
+     */
+    void quiet(Action<TestLogging> action);
+
+    /**
+     * Returns logging options for error level.
+     *
+     * @return logging options for error level
+     */
+    TestLogging getError();
+
+    /**
+     * Sets logging options for error level.
+     *
+     * @param logging logging options for error level
+     */
+    void setError(TestLogging logging);
+
+    /**
+     * Configures logging options for error level.
+     *
+     * @param action logging options for error level
+     */
+    void error(Action<TestLogging> action);
+
+    /**
+     * Returns logging options for the specified level.
+     *
+     * @param level the level whose logging options are to be returned
+     *
+     * @return logging options for the specified level
+     */
+    TestLogging get(LogLevel level);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestStackTraceFilter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestStackTraceFilter.java
new file mode 100644
index 0000000..743fe8d
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/TestStackTraceFilter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 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 org.gradle.api.tasks.testing.logging;
+
+import org.gradle.api.Experimental;
+
+/**
+ * Stack trace filters for test logging. Multiple filters can be combined.
+ */
+ at Experimental
+public enum TestStackTraceFilter {
+    // ordered by how much they filter
+    ENTRY_POINT, TRUNCATE, GROOVY
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/package-info.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/package-info.java
new file mode 100644
index 0000000..c6778f7
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/logging/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 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.
+ */
+
+/**
+ * Types related to logging of test related information to the console.
+ */
+ at Experimental
+package org.gradle.api.tasks.testing.logging;
+
+import org.gradle.api.Experimental;
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
index 3fa6a89..48ce138 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
@@ -23,6 +23,8 @@ import org.gradle.api.tasks.testing.TestFrameworkOptions
  * @author Tom Eyckmans
  */
 public class TestNGOptions extends TestFrameworkOptions implements Serializable {
+    private static final long serialVersionUID = 1
+
     static final String JDK_ANNOTATIONS = 'JDK'
     static final String JAVADOC_ANNOTATIONS = 'Javadoc'
 
diff --git a/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt b/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
index bd7005a..5fb166b 100644
--- a/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+++ b/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
@@ -101,7 +101,7 @@ if [ "\$cygwin" = "false" -a "\$darwin" = "false" ] ; then
             warn "Could not set maximum file descriptor limit: \$MAX_FD"
         fi
     else
-        warn "Could not query businessSystem maximum file descriptor limit: \$MAX_FD_LIMIT"
+        warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT"
     fi
 fi
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java
index 6b683b6..d14c3a7 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.tasks;
 
-import org.gradle.api.internal.DirectInstantiator;
+import org.gradle.internal.reflect.DirectInstantiator;
 import org.gradle.api.tasks.SourceSet;
 import org.junit.Test;
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/ArgWriterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/ArgWriterTest.groovy
new file mode 100755
index 0000000..d7009bb
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/ArgWriterTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.compile
+
+import spock.lang.Specification
+
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
+class ArgWriterTest extends Specification {
+    final StringWriter writer = new StringWriter()
+    final PrintWriter printWriter = new PrintWriter(writer, true)
+    final ArgWriter argWriter = ArgWriter.unixStyle(printWriter)
+
+    def "writes single argument to line"() {
+        when:
+        argWriter.args("-nologo")
+
+        then:
+        writer.toString() == toPlatformLineSeparators("-nologo\n")
+    }
+
+    def "writes multiple arguments to line"() {
+        when:
+        argWriter.args("-I", "some/dir")
+
+        then:
+        writer.toString() == toPlatformLineSeparators("-I some/dir\n")
+    }
+
+    def "quotes argument with whitespace"() {
+        when:
+        argWriter.args("ab c", "d e f")
+
+        then:
+        writer.toString() == toPlatformLineSeparators('"ab c" "d e f"\n')
+    }
+
+    def "escapes double quotes in argument"() {
+        when:
+        argWriter.args('"abc"', 'a" bc')
+
+        then:
+        writer.toString() == toPlatformLineSeparators('\\"abc\\" "a\\" bc"\n')
+    }
+
+    def "escapes backslash in argument"() {
+        when:
+        argWriter.args('a\\b', 'a \\ bc')
+
+        then:
+        writer.toString() == toPlatformLineSeparators('a\\\\b "a \\\\ bc"\n')
+    }
+
+    def "does not escape characters in windows style"() {
+        def argWriter = ArgWriter.windowsStyle(printWriter)
+
+        when:
+        argWriter.args('a\\b', 'a "\\" bc')
+
+        then:
+        writer.toString() == toPlatformLineSeparators('a\\b "a "\\" bc"\n')
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy
index 6e6cf69..071a224 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/CommandLineJavaCompilerArgumentsGeneratorTest.groovy
@@ -52,7 +52,7 @@ class CommandLineJavaCompilerArgumentsGeneratorTest extends Specification {
         Lists.newArrayList(args) == ["-J-Xmx256m", "@$argsFile"]
 
         and: "args file contains remaining arguments (one per line, quoted)"
-        argsFile.readLines() == [quote("-g"), quote("-classpath"), quote("$spec.classpath.asPath"), *(spec.source*.path.collect { quote(it) })]
+        argsFile.readLines() == ["-g", "-classpath", quote("$spec.classpath.asPath"), *(spec.source*.path.collect { quote(it) })]
     }
 
     def createCompileSpec(numFiles) {
@@ -66,7 +66,7 @@ class CommandLineJavaCompilerArgumentsGeneratorTest extends Specification {
     }
 
     def createFiles(numFiles) {
-        (1..numFiles).collect { new File("/foo/bar/File$it") }
+        (1..numFiles).collect { new File("/foo bar/File$it") }
     }
 
     def quote(arg) {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompilerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompilerTest.groovy
new file mode 100644
index 0000000..4e5ae65
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/NormalizingGroovyCompilerTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.compile
+
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+
+import groovy.transform.InheritConstructors
+
+import spock.lang.Specification
+
+class NormalizingGroovyCompilerTest extends Specification { 
+    Compiler<GroovyJavaJointCompileSpec> target = Mock()
+    GroovyJavaJointCompileSpec spec = new DefaultGroovyJavaJointCompileSpec()
+    NormalizingGroovyCompiler compiler = new NormalizingGroovyCompiler(target)
+    
+    def setup() {
+        spec.classpath = files('Dep1.jar', 'Dep2.jar', 'Dep3.jar')
+        spec.groovyClasspath = spec.classpath
+        spec.source = files('House.scala', 'Person1.java', 'package.html', 'Person2.groovy')
+    }
+
+    def "silently excludes source files not ending in .java or .groovy by default"() {
+        when:
+        compiler.execute(spec)
+
+        then:
+        1 * target.execute(spec) >> {
+            assert spec.source.files == files('Person1.java', 'Person2.groovy').files
+        }
+    }
+
+    def "excludes source files that have extension different from specified by fileExtensions option"() {
+        spec.groovyCompileOptions.fileExtensions = ['html']
+
+        when:
+        compiler.execute(spec)
+
+        then:
+        1 * target.execute(spec) >> {
+            assert spec.source.files == files('package.html').files
+        }
+    }
+
+    private files(String... paths) {
+        new TestFileCollection(paths.collect { new File(it) })
+    }
+
+    // file collection whose type is distinguishable from SimpleFileCollection
+    @InheritConstructors
+    static class TestFileCollection extends SimpleFileCollection {} 
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy
index b791c51..6213475 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/TransformingClassLoaderTest.groovy
@@ -19,7 +19,7 @@ package org.gradle.api.internal.tasks.compile
 import spock.lang.Specification
 import org.codehaus.groovy.transform.GroovyASTTransformationClass
 import org.gradle.util.ClasspathUtil
-import org.gradle.util.DefaultClassPath
+import org.gradle.internal.classpath.DefaultClassPath
 
 class TransformingClassLoaderTest extends Specification {
     TransformingClassLoader loader
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessorTest.groovy
index 48ecaad..8155c10 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessorTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.internal.tasks.testing
 
 import spock.lang.Specification
-import org.gradle.util.TimeProvider
+import org.gradle.internal.TimeProvider
 import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor
 
 class SuiteTestClassProcessorTest extends Specification {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
index 1745a53..020d51e 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.api.internal.tasks.testing.TestResultProcessor
 import org.gradle.api.internal.tasks.testing.TestStartEvent
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo
 import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.LongIdGenerator
+import org.gradle.internal.id.LongIdGenerator
 import org.gradle.util.TemporaryFolder
 import org.jmock.integration.junit4.JMock
 import org.junit.Before
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
index b1150d3..0753cd4 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
@@ -17,16 +17,19 @@
 package org.gradle.api.internal.tasks.testing.junit;
 
 import org.gradle.api.AntBuilder;
+import org.gradle.internal.Factory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.AbstractTestFrameworkTest;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.junit.report.TestReporter;
 import org.gradle.api.tasks.testing.junit.JUnitOptions;
 import org.gradle.messaging.actor.ActorFactory;
-import org.gradle.util.IdGenerator;
 import org.jmock.Expectations;
 import org.junit.Before;
 
+import java.io.File;
+
 import static junit.framework.Assert.assertNotNull;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.junit.Assert.assertThat;
@@ -49,12 +52,16 @@ public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
         jUnitOptionsMock = context.mock(JUnitOptions.class);
         idGenerator = context.mock(IdGenerator.class);
         serviceRegistry = context.mock(ServiceRegistry.class);
-
+        final Factory<File> temporaryDirFactory = new Factory<File>() {
+            public File create() {
+                return temporaryDir;
+            }
+        };
         context.checking(new Expectations(){{
             allowing(testMock).getTestClassesDir(); will(returnValue(testClassesDir));
             allowing(testMock).getClasspath(); will(returnValue(classpathMock));
             allowing(testMock).getAnt(); will(returnValue(context.mock(AntBuilder.class)));
-            allowing(testMock).getTemporaryDir(); will(returnValue(temporaryDir));
+            allowing(testMock).getTemporaryDirFactory(); will(returnValue(temporaryDirFactory));
         }});
     }
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGeneratorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGeneratorTest.groovy
index c61296f..3136e32 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGeneratorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/TestClassExecutionEventGeneratorTest.groovy
@@ -17,10 +17,10 @@
 package org.gradle.api.internal.tasks.testing.junit
 
 import org.gradle.api.internal.tasks.testing.TestResultProcessor
-import org.gradle.util.IdGenerator
-import org.gradle.util.TimeProvider
+import org.gradle.internal.id.IdGenerator
+import org.gradle.internal.TimeProvider
 import spock.lang.Specification
-import org.gradle.api.tasks.testing.TestDescriptor
+
 import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
 
 class TestClassExecutionEventGeneratorTest extends Specification {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLoggerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLoggerTest.groovy
new file mode 100644
index 0000000..eb0c136
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/AbstractTestLoggerTest.groovy
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+import org.gradle.logging.StyledTextOutputFactory
+import org.gradle.logging.TestStyledTextOutputFactory
+import org.gradle.util.TextUtil
+
+import spock.lang.Specification
+
+class AbstractTestLoggerTest extends Specification {
+    static String sep = TextUtil.platformLineSeparator
+
+    StyledTextOutputFactory textOutputFactory = new TestStyledTextOutputFactory()
+    AbstractTestLogger logger
+
+    def rootDescriptor = new SimpleTestDescriptor(name: "Test Run", composite: true)
+    def workerDescriptor = new SimpleTestDescriptor(name: "Gradle Worker 2", composite: true, parent: rootDescriptor)
+    def outerSuiteDescriptor = new SimpleTestDescriptor(name: "com.OuterSuiteClass", composite: true, parent: workerDescriptor)
+    def innerSuiteDescriptor = new SimpleTestDescriptor(name: "com.InnerSuiteClass", composite: true, parent: outerSuiteDescriptor)
+    def classDescriptor = new SimpleTestDescriptor(name: "foo.bar.TestClass", composite: true, parent: innerSuiteDescriptor)
+    def methodDescriptor = new SimpleTestDescriptor(name: "testMethod", className: "foo.bar.TestClass", parent: classDescriptor)
+
+    def "log test run event"() {
+        createLogger(LogLevel.INFO)
+
+        when:
+        logger.logEvent(rootDescriptor, TestLogEvent.STARTED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{INFO}${sep}Test Run STARTED${sep}"
+    }
+
+    def "log Gradle worker event"() {
+        createLogger(LogLevel.INFO)
+
+        when:
+        logger.logEvent(workerDescriptor, TestLogEvent.STARTED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{INFO}${sep}Gradle Worker 2 STARTED${sep}"
+    }
+
+    def "log outer suite event"() {
+        createLogger(LogLevel.ERROR)
+
+        when:
+        logger.logEvent(outerSuiteDescriptor, TestLogEvent.STARTED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{ERROR}${sep}com.OuterSuiteClass STARTED${sep}"
+    }
+
+    def "log inner suite event"() {
+        createLogger(LogLevel.QUIET)
+
+        when:
+        logger.logEvent(innerSuiteDescriptor, TestLogEvent.PASSED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{QUIET}${sep}com.OuterSuiteClass > com.InnerSuiteClass {identifier}PASSED{normal}$sep"
+
+    }
+
+    def "log test class event"() {
+        createLogger(LogLevel.WARN)
+
+        when:
+        logger.logEvent(classDescriptor, TestLogEvent.SKIPPED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{WARN}${sep}com.OuterSuiteClass > com.InnerSuiteClass > foo.bar.TestClass {info}SKIPPED{normal}${sep}"
+    }
+
+    def "log test method event"() {
+        createLogger(LogLevel.LIFECYCLE)
+
+        when:
+        logger.logEvent(methodDescriptor, TestLogEvent.FAILED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{LIFECYCLE}${sep}com.OuterSuiteClass > com.InnerSuiteClass > foo.bar.TestClass > testMethod {failure}FAILED{normal}${sep}"
+    }
+
+    def "log standard out event"() {
+        createLogger(LogLevel.INFO)
+
+        when:
+        logger.logEvent(methodDescriptor, TestLogEvent.STANDARD_OUT, "this is a${sep}standard out${sep}event")
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{INFO}${sep}com.OuterSuiteClass > com.InnerSuiteClass > foo.bar.TestClass > testMethod STANDARD_OUT${sep}this is a${sep}standard out${sep}event"
+    }
+
+    def "log standard error event"() {
+        createLogger(LogLevel.DEBUG)
+
+        when:
+        logger.logEvent(methodDescriptor, TestLogEvent.STANDARD_ERROR, "this is a${sep}standard error${sep}event")
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{DEBUG}${sep}com.OuterSuiteClass > com.InnerSuiteClass > foo.bar.TestClass > testMethod STANDARD_ERROR${sep}this is a${sep}standard error${sep}event"
+    }
+
+    def "log test method event with lowest display granularity"() {
+        createLogger(LogLevel.INFO, 0)
+
+        when:
+        logger.logEvent(methodDescriptor, TestLogEvent.FAILED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{INFO}${sep}Test Run > Gradle Worker 2 > com.OuterSuiteClass > com.InnerSuiteClass > foo.bar.TestClass > testMethod {failure}FAILED{normal}${sep}"
+    }
+
+    def "log test method event with highest display granularity"() {
+        createLogger(LogLevel.INFO, -1)
+
+        when:
+        logger.logEvent(methodDescriptor, TestLogEvent.FAILED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{INFO}${sep}testMethod {failure}FAILED{normal}${sep}"
+    }
+
+    def "logging of atomic test whose parent isn't the test class includes test class name"() {
+        createLogger(LogLevel.INFO)
+        methodDescriptor.parent = innerSuiteDescriptor
+
+        when:
+        logger.logEvent(methodDescriptor, TestLogEvent.STARTED)
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{INFO}${sep}com.OuterSuiteClass > com.InnerSuiteClass > foo.bar.TestClass.testMethod STARTED${sep}"
+    }
+
+    def "logs header just once per batch of events with same type and for same test"() {
+        createLogger(LogLevel.INFO)
+
+        when:
+        logger.logEvent(methodDescriptor, TestLogEvent.STANDARD_OUT, "event 1")
+        logger.logEvent(methodDescriptor, TestLogEvent.STANDARD_OUT, "event 2")
+
+        then:
+        textOutputFactory.toString() == "{TestEventLogger}{INFO}${sep}com.OuterSuiteClass > com.InnerSuiteClass\
+ > foo.bar.TestClass > testMethod STANDARD_OUT${sep}event 1{TestEventLogger}{INFO}event 2"
+    }
+
+    void createLogger(LogLevel level, int displayGranularity = 2) {
+        logger = new AbstractTestLogger(textOutputFactory, level, displayGranularity) {}
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ClassMethodNameStackTraceSpecTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ClassMethodNameStackTraceSpecTest.groovy
new file mode 100644
index 0000000..a929b6e
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ClassMethodNameStackTraceSpecTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import spock.lang.Specification
+
+class ClassMethodNameStackTraceSpecTest extends Specification {
+    def "accepts elements whose class and method names match"() {
+        def spec = new ClassMethodNameStackTraceSpec("ClassName", "methodName")
+
+        expect:
+        spec.isSatisfiedBy(new StackTraceElement("ClassName", "methodName", "SomeFile.java", 42))
+        spec.isSatisfiedBy(new StackTraceElement("ClassName", "methodName", "OtherFile.java", 21))
+    }
+
+    def "rejects elements whose class or method name does not match"() {
+        def spec = new ClassMethodNameStackTraceSpec("ClassName", "methodName")
+
+        expect:
+        !spec.isSatisfiedBy(new StackTraceElement("ClassName", "otherMethodName", "SomeFile.java", 42))
+        !spec.isSatisfiedBy(new StackTraceElement("OtherClassName", "methodName", "OtherFile.java", 21))
+    }
+
+    def "allows to only match class name (by setting method name to null)"() {
+        def spec = new ClassMethodNameStackTraceSpec("ClassName", null)
+
+        expect:
+        spec.isSatisfiedBy(new StackTraceElement("ClassName", "methodName", "SomeFile.java", 42))
+        spec.isSatisfiedBy(new StackTraceElement("ClassName", "otherMethodName", "SomeFile.java", 42))
+        !spec.isSatisfiedBy(new StackTraceElement("OtherClassName", "methodName", "OtherFile.java", 21))
+    }
+
+    def "allows to only match method name (by setting class name to null)"() {
+        def spec = new ClassMethodNameStackTraceSpec(null, "methodName")
+
+        expect:
+        spec.isSatisfiedBy(new StackTraceElement("ClassName", "methodName", "SomeFile.java", 42))
+        spec.isSatisfiedBy(new StackTraceElement("OtherClassName", "methodName", "SomeFile.java", 42))
+        !spec.isSatisfiedBy(new StackTraceElement("ClassName", "otherMethodName", "OtherFile.java", 21))
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingContainerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingContainerTest.groovy
new file mode 100644
index 0000000..43a0202
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingContainerTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+import org.gradle.api.tasks.testing.logging.TestStackTraceFilter
+import org.gradle.api.tasks.testing.logging.TestExceptionFormat
+import org.gradle.internal.reflect.DirectInstantiator
+
+import spock.lang.Specification
+import spock.lang.Unroll
+import org.gradle.api.Action
+import org.gradle.api.tasks.testing.logging.TestLogging
+
+class DefaultTestLoggingContainerTest extends Specification {
+    DefaultTestLoggingContainer container = new DefaultTestLoggingContainer(new DirectInstantiator())
+
+    def "sets defaults for level ERROR"() {
+        def logging = container.get(LogLevel.ERROR)
+
+        expect:
+        hasUnchangedDefaults(logging)
+    }
+
+    def "sets defaults for level QUIET"() {
+        def logging = container.get(LogLevel.QUIET)
+
+        expect:
+        hasUnchangedDefaults(logging)
+    }
+
+    def "sets defaults for level WARN"() {
+        def logging = container.get(LogLevel.WARN)
+
+        expect:
+        hasUnchangedDefaults(logging)
+    }
+
+    def "sets defaults for level LIFECYCLE"() {
+        def logging = container.get(LogLevel.LIFECYCLE)
+
+        expect:
+        logging.events == [TestLogEvent.FAILED] as Set
+        logging.minGranularity == -1
+        logging.maxGranularity == -1
+        logging.exceptionFormat == TestExceptionFormat.SHORT
+        logging.showExceptions
+        logging.showCauses
+        logging.stackTraceFilters == [TestStackTraceFilter.TRUNCATE] as Set
+    }
+
+    def "sets defaults for level INFO"() {
+        def logging = container.get(LogLevel.INFO)
+
+        expect:
+        logging.events == [TestLogEvent.FAILED, TestLogEvent.SKIPPED, TestLogEvent.STANDARD_OUT, TestLogEvent.STANDARD_ERROR] as Set
+        logging.minGranularity == -1
+        logging.maxGranularity == -1
+        logging.exceptionFormat == TestExceptionFormat.FULL
+        logging.showExceptions
+        logging.showCauses
+        logging.stackTraceFilters == [TestStackTraceFilter.TRUNCATE] as Set
+    }
+
+    def "sets defaults for level DEBUG"() {
+        def logging = container.get(LogLevel.DEBUG)
+
+        expect:
+        logging.events == EnumSet.allOf(TestLogEvent)
+        logging.minGranularity == 0
+        logging.maxGranularity == -1
+        logging.exceptionFormat == TestExceptionFormat.FULL
+        logging.showExceptions
+        logging.showCauses
+        logging.stackTraceFilters == [] as Set
+    }
+
+    def "implicitly configures level LIFECYCLE"() {
+        def logging = container.get(LogLevel.LIFECYCLE)
+        assert logging.showExceptions
+
+        when:
+        container.showExceptions = false
+
+        then:
+        !logging.showExceptions
+    }
+
+    @Unroll
+    def "allows to explicitly configure level #level"(LogLevel level) {
+        def logging = container.get(level)
+        def configMethod = level.toString().toLowerCase()
+
+        when:
+        container."$configMethod"(new Action<TestLogging>() {
+            void execute(TestLogging l) {
+                l.showExceptions = false
+            }
+        })
+
+        then:
+        !logging.showExceptions
+
+        where:
+        level << LogLevel.values()
+    }
+
+    private void hasUnchangedDefaults(logging) {
+        assert logging.events == [] as Set
+        assert logging.minGranularity == -1
+        assert logging.maxGranularity == -1
+        logging.exceptionFormat == TestExceptionFormat.FULL
+        logging.showExceptions
+        logging.showCauses
+        assert logging.stackTraceFilters == [TestStackTraceFilter.TRUNCATE] as Set
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingTest.groovy
new file mode 100644
index 0000000..c696929
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/DefaultTestLoggingTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import spock.lang.Specification
+
+import static org.gradle.api.tasks.testing.logging.TestStackTraceFilter.*
+import static org.gradle.api.tasks.testing.logging.TestLogEvent.*
+
+class DefaultTestLoggingTest extends Specification {
+    private logging = new DefaultTestLogging()
+
+    def "has defaults"() {
+        expect:
+        logging.events == [] as Set
+        logging.minGranularity == -1
+        logging.maxGranularity == -1
+        logging.stackTraceFilters == [TRUNCATE] as Set
+    }
+
+    def "allows events to be added as enum values"() {
+        when:
+        logging.events STARTED, SKIPPED
+
+        then:
+        logging.events == [STARTED, SKIPPED] as Set
+    }
+
+    def "allows events to be added as string values"() {
+        when:
+        logging.events "started", "skipped"
+
+        then:
+        logging.events == [STARTED, SKIPPED] as Set
+    }
+
+    def "allows stack trace formats to be added as enum values"() {
+        when:
+        logging.stackTraceFilters TRUNCATE, GROOVY
+
+        then:
+        logging.stackTraceFilters == [TRUNCATE, GROOVY] as Set
+    }
+
+    def "allows stack trace formats to be added as string values"() {
+        when:
+        logging.stackTraceFilters "truncate", "groovy"
+
+        then:
+        logging.stackTraceFilters == [TRUNCATE, GROOVY] as Set
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy
new file mode 100644
index 0000000..86ef784
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/FullExceptionFormatterTest.groovy
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import spock.lang.Specification
+import org.gradle.api.tasks.testing.logging.TestLogging
+import org.gradle.api.tasks.testing.logging.TestStackTraceFilter
+import org.gradle.messaging.remote.internal.PlaceholderException
+
+class FullExceptionFormatterTest extends Specification {
+    def testDescriptor = new SimpleTestDescriptor()
+    def testLogging = Mock(TestLogging)
+    def formatter = new FullExceptionFormatter(testLogging)
+
+    def "shows all exceptions that have occurred for a test"() {
+        expect:
+        formatter.format(testDescriptor, [new RuntimeException("oops"), new Exception("ouch")]) == """\
+    java.lang.RuntimeException: oops
+
+    java.lang.Exception: ouch
+"""
+    }
+
+    def "optionally shows causes"() {
+        testLogging.getShowCauses() >> true
+        def cause = new RuntimeException("oops")
+        def exception = new Exception("ouch", cause)
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+
+        Caused by:
+        java.lang.RuntimeException: oops
+"""
+    }
+
+    def "optionally shows stack traces"() {
+        testLogging.getShowStackTraces() >> true
+        testLogging.getStackTraceFilters() >> EnumSet.noneOf(TestStackTraceFilter)
+        def exception = new Exception("ouch")
+        exception.stackTrace = createStackTrace()
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+        at org.ClassName1.methodName1(FileName1.java:11)
+        at org.ClassName2.methodName2(FileName2.java:22)
+        at org.ClassName3.methodName3(FileName3.java:33)
+"""
+    }
+
+    def "doesn't show common stack trace elements of parent trace and cause"() {
+        testLogging.getShowCauses() >> true
+        testLogging.getShowStackTraces() >> true
+        testLogging.getStackTraceFilters() >> EnumSet.noneOf(TestStackTraceFilter)
+
+        def cause = new RuntimeException("oops")
+        cause.stackTrace = createCauseTrace()
+        def exception = new Exception("ouch", cause)
+        exception.stackTrace = createStackTrace()
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+        at org.ClassName1.methodName1(FileName1.java:11)
+        at org.ClassName2.methodName2(FileName2.java:22)
+        at org.ClassName3.methodName3(FileName3.java:33)
+
+        Caused by:
+        java.lang.RuntimeException: oops
+            at org.ClassName0.methodName0(FileName0.java:1)
+            at org.ClassName1.methodName1(FileName1.java:10)
+            ... 2 more
+"""
+    }
+
+    def "always shows at least one stack trace element of cause"() {
+        testLogging.getShowCauses() >> true
+        testLogging.getShowStackTraces() >> true
+        testLogging.getStackTraceFilters() >> EnumSet.noneOf(TestStackTraceFilter)
+
+        def cause = new RuntimeException("oops")
+        cause.stackTrace = createStackTrace()
+        def exception = new Exception("ouch", cause)
+        exception.stackTrace = createStackTrace()
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+        at org.ClassName1.methodName1(FileName1.java:11)
+        at org.ClassName2.methodName2(FileName2.java:22)
+        at org.ClassName3.methodName3(FileName3.java:33)
+
+        Caused by:
+        java.lang.RuntimeException: oops
+            at org.ClassName1.methodName1(FileName1.java:11)
+            ... 2 more
+"""
+    }
+
+    def "can cope with a cause that has fewer stack trace elements than parent exception"() {
+        testLogging.getShowCauses() >> true
+        testLogging.getShowStackTraces() >> true
+        testLogging.getStackTraceFilters() >> EnumSet.noneOf(TestStackTraceFilter)
+
+        def cause = new RuntimeException("oops")
+        cause.stackTrace = createStackTrace()[1..2]
+        def exception = new Exception("ouch", cause)
+        exception.stackTrace = createStackTrace()
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+        at org.ClassName1.methodName1(FileName1.java:11)
+        at org.ClassName2.methodName2(FileName2.java:22)
+        at org.ClassName3.methodName3(FileName3.java:33)
+
+        Caused by:
+        java.lang.RuntimeException: oops
+            at org.ClassName2.methodName2(FileName2.java:22)
+            ... 1 more
+"""
+    }
+
+    def "shows all stack trace elements of cause if overlap doesn't start from bottom of trace"() {
+        testLogging.getShowCauses() >> true
+        testLogging.getShowStackTraces() >> true
+        testLogging.getStackTraceFilters() >> EnumSet.noneOf(TestStackTraceFilter)
+
+        def cause = new RuntimeException("oops")
+        cause.stackTrace = createStackTrace()[0..1]
+        def exception = new Exception("ouch", cause)
+        exception.stackTrace = createStackTrace()
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+        at org.ClassName1.methodName1(FileName1.java:11)
+        at org.ClassName2.methodName2(FileName2.java:22)
+        at org.ClassName3.methodName3(FileName3.java:33)
+
+        Caused by:
+        java.lang.RuntimeException: oops
+            at org.ClassName1.methodName1(FileName1.java:11)
+            at org.ClassName2.methodName2(FileName2.java:22)
+"""
+    }
+
+    def "supports any combination of stack trace filters"() {
+        testLogging.getShowStackTraces() >> true
+        testLogging.getStackTraceFilters() >> EnumSet.of(TestStackTraceFilter.TRUNCATE, TestStackTraceFilter.GROOVY)
+
+        def exception = new Exception("ouch")
+        exception.stackTrace = createGroovyTrace()
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+        at org.ClassName1.methodName1(FileName1.java:11)
+        at org.ClassName2.methodName2(FileName2.java:22)
+        at ClassName.testName(MyTest.java:22)
+"""
+    }
+
+    def "also filters stack traces of causes"() {
+        testLogging.getShowCauses() >> true
+        testLogging.getShowStackTraces() >> true
+        testLogging.getStackTraceFilters() >> EnumSet.of(TestStackTraceFilter.ENTRY_POINT)
+
+        def cause = new RuntimeException("oops")
+        cause.stackTrace = createGroovyTrace()
+
+        def exception = new Exception("ouch", cause)
+        exception.stackTrace = createGroovyTrace()[1..-1]
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+        at ClassName.testName(MyTest.java:22)
+
+        Caused by:
+        java.lang.RuntimeException: oops
+            at ClassName.testName(MyTest.java:22)
+"""
+    }
+
+    def "formats PlaceholderException's correctly"() {
+        testLogging.getShowCauses() >> true
+        testLogging.getShowStackTraces() >> true
+        testLogging.getStackTraceFilters() >> EnumSet.of(TestStackTraceFilter.ENTRY_POINT)
+
+        def cause = new PlaceholderException(RuntimeException.name, "oops", null)
+        cause.stackTrace = createGroovyTrace()
+
+        def exception = new PlaceholderException(Exception.name, "ouch", cause)
+        exception.stackTrace = createGroovyTrace()[1..-1]
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception: ouch
+        at ClassName.testName(MyTest.java:22)
+
+        Caused by:
+        java.lang.RuntimeException: oops
+            at ClassName.testName(MyTest.java:22)
+"""
+    }
+
+    private createStackTrace() {
+        [
+                new StackTraceElement("org.ClassName1", "methodName1", "FileName1.java", 11),
+                new StackTraceElement("org.ClassName2", "methodName2", "FileName2.java", 22),
+                new StackTraceElement("org.ClassName3", "methodName3", "FileName3.java", 33)
+        ] as StackTraceElement[]
+    }
+
+    private createCauseTrace() {
+        [
+                new StackTraceElement("org.ClassName0", "methodName0", "FileName0.java", 1),
+                new StackTraceElement("org.ClassName1", "methodName1", "FileName1.java", 10),
+                new StackTraceElement("org.ClassName2", "methodName2", "FileName2.java", 22),
+                new StackTraceElement("org.ClassName3", "methodName3", "FileName3.java", 33)
+        ] as StackTraceElement[]
+    }
+
+    private createGroovyTrace() {
+        [
+                new StackTraceElement("org.ClassName1", "methodName1", "FileName1.java", 11),
+                new StackTraceElement("java.lang.reflect.Method", "invoke", "Method.java", 597),
+                new StackTraceElement("org.ClassName2", "methodName2", "FileName2.java", 22),
+                // class and method name match SimpleTestDescriptor
+                new StackTraceElement("ClassName", "testName", "MyTest.java", 22),
+                new StackTraceElement("java.lang.reflect.Method", "invoke", "Method.java", 597),
+                new StackTraceElement("org.ClassName3", "methodName3", "FileName3.java", 33)
+        ] as StackTraceElement[]
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/GroovyStackTraceSpecTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/GroovyStackTraceSpecTest.groovy
new file mode 100644
index 0000000..60f5fb2
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/GroovyStackTraceSpecTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import spock.lang.Specification
+
+class GroovyStackTraceSpecTest extends Specification {
+    def "removes Groovy internals from stack trace"() {
+        def spec = new GroovyStackTraceSpec()
+        def filter = new StackTraceFilter(spec)
+        def trace = createTrace()
+
+        when:
+        def filtered = filter.filter(trace)
+
+        then:
+        filtered.size() == 7
+        filtered[0] == new StackTraceElement("org.gradle.api.internal.tasks.testing.logging.GroovyStackTraceSpecTest\$Baz", "getBoom", "GroovyStackTraceSpecTest.groovy", 74)
+        filtered[1] == new StackTraceElement("org.gradle.api.internal.tasks.testing.logging.GroovyStackTraceSpecTest\$SuperBaz", "setBaz", "GroovyStackTraceSpecTest.groovy", 62)
+        filtered[2] == new StackTraceElement("org.gradle.api.internal.tasks.testing.logging.GroovyStackTraceSpecTest\$Baz", "setBaz", "GroovyStackTraceSpecTest.groovy", 70)
+        filtered[3] == new StackTraceElement("org.gradle.api.internal.tasks.testing.logging.GroovyStackTraceSpecTest\$Bar", "bar", "GroovyStackTraceSpecTest.groovy", 56)
+        filtered[4] == new StackTraceElement("org.gradle.api.internal.tasks.testing.logging.GroovyStackTraceSpecTest", "foo", "GroovyStackTraceSpecTest.groovy", 51)
+        filtered[5] == new StackTraceElement("org.gradle.api.internal.tasks.testing.logging.GroovyStackTraceSpecTest", "createTrace", "GroovyStackTraceSpecTest.groovy", 42)
+    }
+
+    private List<StackTraceElement> createTrace() {
+        try {
+            foo()
+            throw new AssertionError()
+        } catch (Exception e) {
+            def filter = new StackTraceFilter(new TruncatedStackTraceSpec(new ClassMethodNameStackTraceSpec(getClass().getName(), null)))
+            return filter.filter(e)
+        }
+    }
+
+    private void foo() {
+        Bar.bar()
+    }
+
+    static class Bar {
+        static void bar() {
+            new Baz().baz = 42
+        }
+    }
+
+    static abstract class SuperBaz {
+        void setBaz(int baz) {
+            boom
+        }
+
+        abstract String getBoom()
+    }
+
+    static class Baz extends SuperBaz {
+        void setBaz(int baz) {
+            super.setBaz(baz)
+        }
+
+        String getBoom() {
+            throw new Exception("boom")
+        }
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy
new file mode 100644
index 0000000..c58c125
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/ShortExceptionFormatterTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import spock.lang.Specification
+import org.gradle.api.tasks.testing.logging.TestLogging
+import org.gradle.messaging.remote.internal.PlaceholderException
+
+class ShortExceptionFormatterTest extends Specification {
+    def testDescriptor = new SimpleTestDescriptor()
+    def testLogging = Mock(TestLogging)
+    def formatter = new ShortExceptionFormatter(testLogging)
+
+    def "shows all exceptions that have occurred for a test"() {
+        expect:
+        formatter.format(testDescriptor, [new IOException("oops"), new AssertionError("ouch")]) == """\
+    java.io.IOException
+    java.lang.AssertionError
+"""
+    }
+
+    def "shows test entry point if it can be determined"() {
+        def exception = new Exception("oops")
+        testDescriptor.className = getClass().name
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception at ShortExceptionFormatterTest.groovy:37
+"""
+    }
+
+    def "optionally shows causes"() {
+        def causeCause = new RuntimeException("oops")
+        def cause = new IllegalArgumentException("ouch", causeCause)
+        def exception = new Exception("argh", cause)
+
+        testLogging.showCauses >> true
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception
+        Caused by: java.lang.IllegalArgumentException
+            Caused by: java.lang.RuntimeException
+"""
+    }
+
+    def "formats PlaceholderException's correctly"() {
+        def exception = new PlaceholderException(Exception.class.name, "oops", null)
+        testDescriptor.className = getClass().name
+
+        expect:
+        formatter.format(testDescriptor, [exception]) == """\
+    java.lang.Exception at ShortExceptionFormatterTest.groovy:62
+"""
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestDescriptor.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestDescriptor.groovy
new file mode 100644
index 0000000..79e5a8c
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestDescriptor.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import org.gradle.api.tasks.testing.TestDescriptor
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
+
+class SimpleTestDescriptor implements TestDescriptorInternal {
+    String name = "testName"
+    String className = "ClassName"
+    boolean composite = false
+    TestDescriptor parent = null
+    Object getId() {
+        "${parent?.id}$className$name" as String
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestOutputEvent.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestOutputEvent.groovy
new file mode 100644
index 0000000..b19dd3c
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestOutputEvent.groovy
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.api.tasks.testing.TestOutputEvent.Destination
+
+class SimpleTestOutputEvent implements TestOutputEvent {
+    Destination destination = Destination.StdOut
+    String message = "message to standard out"
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestResult.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestResult.groovy
new file mode 100644
index 0000000..9b4cdb4
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/SimpleTestResult.groovy
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import org.gradle.api.tasks.testing.TestResult
+
+class SimpleTestResult implements TestResult {
+    TestResult.ResultType resultType = TestResult.ResultType.SUCCESS
+    List<Throwable> exceptions = []
+    Throwable exception = exceptions[0]
+    long startTime = System.currentTimeMillis()
+    long endTime = startTime + 100
+    long testCount = 1
+    long successfulTestCount = 1
+    long failedTestCount = 0
+    long skippedTestCount = 0
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StackTraceFilterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StackTraceFilterTest.groovy
new file mode 100644
index 0000000..2f75105
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StackTraceFilterTest.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import spock.lang.Specification
+import org.gradle.api.specs.Spec
+
+class StackTraceFilterTest extends Specification {
+    def spec = Mock(Spec)
+    def filter = new StackTraceFilter(spec)
+    def exception = new Exception()
+    def trace = exception.stackTrace as List
+
+    def "returns a new trace rather than mutating the original one"() {
+        spec.isSatisfiedBy(_) >> true
+
+        when:
+        def filtered = filter.filter(trace)
+
+        then:
+        !filtered.is(trace)
+        trace == old(trace)
+    }
+
+    def "filters stack trace according to the provided spec"() {
+        spec.isSatisfiedBy(_) >> { StackTraceElement elem -> elem.methodName.size() > 10 }
+
+        when:
+        def filtered = filter.filter(trace)
+
+        then:
+        filtered.size() > 0
+        filtered.size() < trace.size()
+        filtered == trace.findAll { StackTraceElement elem -> elem.methodName.size() > 10 }
+    }
+
+    def "filters stack trace in invocation order (bottom to top)"() {
+        trace = [
+                new StackTraceElement("ClassName", "methodName", "FileName.java", 1),
+                new StackTraceElement("ClassName", "methodName", "FileName.java", 2)
+        ]
+
+        when:
+        filter.filter(trace)
+
+        then:
+        1 * spec.isSatisfiedBy({ it.lineNumber == 2 })
+
+        then:
+        1 * spec.isSatisfiedBy({ it.lineNumber == 1 })
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLoggerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLoggerTest.groovy
deleted file mode 100644
index 9dffe7e..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/StandardStreamsLoggerTest.groovy
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright 2011 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 org.gradle.api.internal.tasks.testing.logging;
-
-
-import org.gradle.api.internal.tasks.testing.DefaultTestDescriptor
-import org.gradle.api.internal.tasks.testing.DefaultTestOutputEvent
-import org.gradle.api.tasks.testing.TestLogging
-import org.gradle.api.tasks.testing.TestOutputEvent.Destination
-import org.slf4j.Logger
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 11/4/11
- */
-public class StandardStreamsLoggerTest extends Specification {
-
-    def logger = Mock(Logger)
-    def test = new DefaultTestDescriptor("1", "DogTest", "should bark")
-    def event = new DefaultTestOutputEvent(Destination.StdOut, "woof!")
-
-    def "does not show standard streams"() {
-        given:
-        def streamsLogger = new StandardStreamsLogger(logger, { showStandardStreams: false } as TestLogging)
-
-        when:
-        streamsLogger.onOutput(test, event)
-
-        then:
-        0 * logger._
-    }
-
-    def "includes header once"() {
-        given:
-        def streamsLogger = new StandardStreamsLogger(logger, { showStandardStreams: true } as TestLogging)
-
-        when:
-        streamsLogger.onOutput(test, event)
-
-        then:
-        1 * logger.info({ it.contains("should bark") })
-        1 * logger.info({ it.contains("woof!")})
-
-        when:
-        streamsLogger.onOutput(test, event)
-        streamsLogger.onOutput(test, event)
-
-        then:
-        2 * logger.info({ !it.contains("should bark") && it.contains("woof!")})
-    }
-
-    def "includes header once per series of output events"() {
-        given:
-        def testTwo = new DefaultTestDescriptor("2", "DogTest", "should growl")
-        def eventTwo = new DefaultTestOutputEvent(Destination.StdOut, "grrr!")
-        def streamsLogger = new StandardStreamsLogger(logger, { showStandardStreams: true } as TestLogging)
-
-        when:
-        streamsLogger.onOutput(test, event)
-        streamsLogger.onOutput(test, event)
-
-        then:
-        1 * logger.info({ it.contains("should bark") })
-        2 * logger.info({ !it.contains("should bark") && it.contains("woof!")})
-        0 * logger._
-
-        when:
-        streamsLogger.onOutput(testTwo, eventTwo)
-        streamsLogger.onOutput(testTwo, eventTwo)
-
-        then:
-        1 * logger.info({ it.contains("should growl") })
-        2 * logger.info({ !it.contains("should growl") && it.contains("grrr!")})
-        0 * logger._
-
-        when:
-        //let's say test one is still pushing some output, include the header accordingly
-        streamsLogger.onOutput(test, event)
-        streamsLogger.onOutput(test, event)
-
-        then:
-        1 * logger.info({ it.contains("should bark") })
-        2 * logger.info({ !it.contains("should bark") && it.contains("woof!")})
-        0 * logger._
-    }
-
-    def "uses error level for errors"() {
-        def streamsLogger = new StandardStreamsLogger(logger, { showStandardStreams: true } as TestLogging)
-        def event = new DefaultTestOutputEvent(Destination.StdErr, "boom!")
-
-        when:
-        streamsLogger.onOutput(test, event)
-
-        then:
-        1 * logger.info({ it.contains("should bark")})
-        1 * logger.error({ it.contains("boom!")})
-        0 * logger._
-    }
-}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestCountLoggerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestCountLoggerTest.groovy
new file mode 100644
index 0000000..3d7b37a
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestCountLoggerTest.groovy
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2010 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 org.gradle.api.internal.tasks.testing.logging
+
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.logging.ProgressLogger
+import org.gradle.api.tasks.testing.TestDescriptor
+import org.gradle.api.tasks.testing.TestResult
+import org.slf4j.Logger
+
+import spock.lang.Specification
+import org.gradle.util.TextUtil
+
+class TestCountLoggerTest extends Specification {
+    private final ProgressLoggerFactory factory = Mock()
+    private final ProgressLogger progressLogger = Mock()
+    private final TestDescriptor rootSuite = suite(true)
+    private final Logger errorLogger = Mock()
+    private final TestCountLogger logger = new TestCountLogger(factory, errorLogger)
+    private final String sep = TextUtil.platformLineSeparator
+
+    def setup() {
+        factory.newOperation(TestCountLogger) >> progressLogger
+    }
+
+    def startsProgressLoggerWhenRootSuiteIsStartedAndStopsWhenRootSuiteIsCompleted() {
+        when:
+        logger.beforeSuite(rootSuite)
+
+        then:
+        1 * progressLogger.started()
+
+        when:
+        logger.afterSuite(rootSuite, result())
+
+        then:
+        1 * progressLogger.completed()
+    }
+
+    def logsCountOfTestsExecuted() {
+        TestDescriptor test1 = test()
+        TestDescriptor test2 = test()
+
+        logger.beforeSuite(rootSuite)
+
+        when:
+        logger.afterTest(test1, result())
+
+        then:
+        1 * progressLogger.progress('1 test completed')
+
+        when:
+        logger.afterTest(test2, result())
+
+        then:
+        1 * progressLogger.progress('2 tests completed')
+
+        when:
+        logger.afterSuite(rootSuite, result())
+
+        then:
+        1 * progressLogger.completed()
+    }
+
+    def logsCountOfFailedTests() {
+        TestDescriptor test1 = test()
+        TestDescriptor test2 = test()
+
+        logger.beforeSuite(rootSuite)
+
+        when:
+        logger.afterTest(test1, result())
+
+        then:
+        1 * progressLogger.progress('1 test completed')
+
+        when:
+        logger.afterTest(test2, result(true))
+
+        then:
+        1 * progressLogger.progress('2 tests completed, 1 failed')
+
+        when:
+        logger.afterSuite(rootSuite, result())
+
+        then:
+        1 * errorLogger.error("${sep}2 tests completed, 1 failed")
+        1 * progressLogger.completed()
+    }
+
+    def ignoresSuitesOtherThanTheRootSuite() {
+        TestDescriptor suite = suite()
+
+        logger.beforeSuite(rootSuite)
+
+        when:
+        logger.beforeSuite(suite)
+        logger.afterSuite(suite, result())
+
+        then:
+        0 * progressLogger._
+
+        when:
+        logger.afterSuite(rootSuite, result())
+
+        then:
+        1 * progressLogger.completed()
+    }
+
+    def "remembers whether root suite reported failure"() {
+        when:
+        logger.beforeSuite(rootSuite)
+        logger.afterSuite(rootSuite, result())
+
+        then:
+        !logger.hadFailures()
+
+        when:
+        logger.beforeSuite(rootSuite)
+        logger.afterSuite(rootSuite, result(true))
+
+        then:
+        logger.hadFailures()
+    }
+
+    private test() {
+        [:] as TestDescriptor
+    }
+    
+    private suite(boolean root = false) {
+        [getParent: {root ? null : [:] as TestDescriptor}] as TestDescriptor
+    }
+
+    private result(boolean failed = false) {
+        [getTestCount: { 1L }, getFailedTestCount: { failed ? 1L : 0L }, getSkippedTestCount: { 0L },
+                getResultType: { failed ? TestResult.ResultType.FAILURE : TestResult.ResultType.SUCCESS }] as TestResult
+    }
+}
+
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLoggerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLoggerTest.groovy
new file mode 100644
index 0000000..2c4b407
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TestEventLoggerTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.tasks.testing.logging.TestLogEvent
+import org.gradle.api.tasks.testing.TestResult
+
+import spock.lang.Specification
+import org.gradle.logging.TestStyledTextOutputFactory
+
+class TestEventLoggerTest extends Specification {
+    def textOutputFactory = new TestStyledTextOutputFactory()
+
+    def testLogging = new DefaultTestLogging()
+    def exceptionFormatter = Mock(TestExceptionFormatter)
+    def eventLogger = new TestEventLogger(textOutputFactory, LogLevel.INFO, testLogging, exceptionFormatter)
+
+    def rootDescriptor = new SimpleTestDescriptor(name: "", composite: true)
+    def workerDescriptor = new SimpleTestDescriptor(name: "worker", composite: true, parent: rootDescriptor)
+    def outerSuiteDescriptor = new SimpleTestDescriptor(name: "com.OuterSuiteClass", composite: true, parent: workerDescriptor)
+    def innerSuiteDescriptor = new SimpleTestDescriptor(name: "com.InnerSuiteClass", composite: true, parent: outerSuiteDescriptor)
+    def classDescriptor = new SimpleTestDescriptor(name: "foo.bar.TestClass", composite: true, parent: innerSuiteDescriptor)
+    def methodDescriptor = new SimpleTestDescriptor(name: "testMethod", className: "foo.bar.TestClass", parent: classDescriptor)
+
+    def result = new SimpleTestResult()
+
+    def "logs event if event type matches"() {
+        testLogging.events(TestLogEvent.PASSED, TestLogEvent.SKIPPED)
+
+        when:
+        eventLogger.afterTest(methodDescriptor, result)
+
+        then:
+        textOutputFactory.toString().count("PASSED") == 1
+
+        when:
+        textOutputFactory.clear()
+        result.resultType = TestResult.ResultType.FAILURE
+        eventLogger.afterTest(methodDescriptor, result)
+
+        then:
+        textOutputFactory.toString().count("PASSED") == 0
+    }
+
+    def "logs event if granularity matches"() {
+        testLogging.events(TestLogEvent.PASSED)
+        testLogging.minGranularity = 2
+        testLogging.maxGranularity = 4
+
+        when:
+        eventLogger.afterSuite(outerSuiteDescriptor, result)
+        eventLogger.afterSuite(innerSuiteDescriptor, result)
+        eventLogger.afterSuite(classDescriptor, result)
+
+        then:
+        textOutputFactory.toString().count("PASSED") == 3
+
+        when:
+        textOutputFactory.clear()
+        eventLogger.afterSuite(rootDescriptor, result)
+        eventLogger.afterSuite(workerDescriptor, result)
+        eventLogger.afterTest(methodDescriptor, result)
+
+        then:
+        textOutputFactory.toString().count("PASSED") == 0
+    }
+
+    def "shows exceptions if configured"() {
+        testLogging.events(TestLogEvent.FAILED)
+        result.resultType = TestResult.ResultType.FAILURE
+        result.exceptions = [new RuntimeException()]
+
+        exceptionFormatter.format(*_) >> "formatted exception"
+
+        when:
+        testLogging.showExceptions = true
+        eventLogger.afterTest(methodDescriptor, result)
+
+        then:
+        textOutputFactory.toString().contains("formatted exception")
+
+        when:
+        textOutputFactory.clear()
+        testLogging.showExceptions = false
+        eventLogger.afterTest(methodDescriptor, result)
+
+        then:
+        !textOutputFactory.toString().contains("formatted exception")
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TruncatedStackTraceSpecTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TruncatedStackTraceSpecTest.groovy
new file mode 100644
index 0000000..768d5a9
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/logging/TruncatedStackTraceSpecTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 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 org.gradle.api.internal.tasks.testing.logging
+
+import spock.lang.Specification
+import org.gradle.api.specs.Spec
+
+class TruncatedStackTraceSpecTest extends Specification {
+    def "accepts all elements from truncation point onwards"() {
+        def truncationPointDetector = Mock(Spec)
+        truncationPointDetector.isSatisfiedBy(_) >>> [false, false, true, false]
+
+        def spec = new TruncatedStackTraceSpec(truncationPointDetector)
+        def element = new StackTraceElement("foo", "bar", "baz", 42)
+
+        expect:
+        !spec.isSatisfiedBy(element)
+        !spec.isSatisfiedBy(element)
+        spec.isSatisfiedBy(element)
+        spec.isSatisfiedBy(element)
+        spec.isSatisfiedBy(element)
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy
index 9c26a31..c0cd2a1 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy
@@ -23,7 +23,7 @@ package org.gradle.api.internal.tasks.testing.processors
 
 import org.gradle.api.internal.tasks.testing.TestClassProcessor
 import org.gradle.util.JUnit4GroovyMockery
-import org.gradle.util.TimeProvider
+import org.gradle.internal.TimeProvider
 import org.jmock.integration.junit4.JMock
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy
index 310e567..e063e89 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy
@@ -16,11 +16,13 @@
 package org.gradle.api.internal.tasks.testing.results
 
 import org.gradle.api.tasks.testing.TestResult.ResultType
+import org.gradle.api.internal.tasks.testing.*
+import org.gradle.api.tasks.testing.*
+
 import org.junit.Test
+
 import spock.lang.Issue
 import spock.lang.Specification
-import org.gradle.api.internal.tasks.testing.*
-import org.gradle.api.tasks.testing.*
 
 class TestListenerAdapterTest extends Specification {
 
@@ -37,7 +39,7 @@ class TestListenerAdapterTest extends Specification {
 
         then:
         1 * listener.beforeTest({ it instanceof DecoratingTestDescriptor && it.descriptor == test })
-        0 * _._
+        0 * _
     }
 
     public void notifiesAfter() {
@@ -54,7 +56,7 @@ class TestListenerAdapterTest extends Specification {
                 { it instanceof DecoratingTestDescriptor && it.descriptor == test },
                 { it.successfulTestCount == 1 && it.testCount == 1 && it.failedTestCount == 0 }
         )
-        0 * _._
+        0 * _
     }
 
     public void createsAResultForATestWithFailure() {
@@ -71,7 +73,7 @@ class TestListenerAdapterTest extends Specification {
         1 * listener.beforeTest(_)
         1 * listener.afterTest({ it.descriptor == test },
                 { it.successfulTestCount == 0 && it.testCount == 1 && it.failedTestCount == 1 && it.exception.is(failure) })
-        0 * _._
+        0 * _
     }
 
     public void createsAResultForATestWithMultipleFailures() {
@@ -104,7 +106,7 @@ class TestListenerAdapterTest extends Specification {
         1 * listener.afterSuite({it.descriptor == suite}, {
             it.testCount == 0 && it.resultType == ResultType.SUCCESS
         })
-        0 * _._
+        0 * _
     }
 
     public void createsAnAggregateResultForTestSuiteWithPassedTest() {
@@ -123,7 +125,7 @@ class TestListenerAdapterTest extends Specification {
         1 * listener.beforeTest({it.descriptor == test})
         1 * listener.afterTest({it.descriptor == test}, _ as TestResult)
         1 * listener.afterSuite({it.descriptor == suite}, { it.testCount == 1 })
-        0 * _._
+        0 * _
     }
 
     public void createsAnAggregateResultForTestSuiteWithFailedTest() {
@@ -148,7 +150,7 @@ class TestListenerAdapterTest extends Specification {
         1 * listener.afterTest({it.descriptor == ok}, _ as TestResult)
         1 * listener.afterTest({it.descriptor == broken}, _ as TestResult)
         1 * listener.afterSuite({it.descriptor == suite}, { it.testCount == 2 && it.failedTestCount == 1 && it.successfulTestCount == 1 })
-        0 * _._
+        0 * _
     }
 
     public void createsAnAggregateResultForTestSuiteWithSkippedTest() {
@@ -168,7 +170,7 @@ class TestListenerAdapterTest extends Specification {
         1 * listener.afterTest({it.descriptor == test}, _ as TestResult)
         1 * listener.afterSuite({it.descriptor == suite},
                 { it.resultType == ResultType.SUCCESS && it.testCount == 1 && it.failedTestCount == 0 && it.successfulTestCount == 0 })
-        0 * _._
+        0 * _
     }
 
     @Test
@@ -209,7 +211,7 @@ class TestListenerAdapterTest extends Specification {
         1 * listener.afterSuite({it.descriptor == suite1}, { it.successfulTestCount == 1 && it.testCount == 1 && it.resultType == ResultType.SUCCESS})
         1 * listener.afterSuite({it.descriptor == suite2}, { it.successfulTestCount == 0 && it.testCount == 1 && it.resultType == ResultType.FAILURE})
 
-        0 * _._
+        0 * _
     }
 
     public void createsAnAggregateResultForTestSuiteWithFailure() {
@@ -231,7 +233,7 @@ class TestListenerAdapterTest extends Specification {
         1 * listener.afterTest({it.descriptor == test}, _ as TestResult)
         1 * listener.afterSuite({it.descriptor == suite},
                 { it.resultType == ResultType.FAILURE && it.exception.is(failure) && it.exceptions == [failure] })
-        0 * _._
+        0 * _
     }
 
     def "notifies output listener"() {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestLoggerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestLoggerTest.groovy
deleted file mode 100644
index 511bd38..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestLoggerTest.groovy
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal.tasks.testing.results
-
-import spock.lang.Specification
-import org.gradle.logging.ProgressLoggerFactory
-import org.gradle.logging.ProgressLogger
-import org.gradle.api.tasks.testing.TestDescriptor
-import org.gradle.api.tasks.testing.TestResult
-import org.slf4j.Logger
-
-class TestLoggerTest extends Specification {
-    private final ProgressLoggerFactory factory = Mock()
-    private final ProgressLogger progressLogger = Mock()
-    private final TestDescriptor rootSuite = suite(true)
-    private final Logger errorLogger = Mock()
-    private final TestLogger logger = new TestLogger(factory, errorLogger)
-
-    def startsProgressLoggerWhenRootSuiteIsStartedAndStopsWhenRootSuiteIsCompleted() {
-        when:
-        logger.beforeSuite(rootSuite)
-
-        then:
-        1 * factory.newOperation(TestLogger) >> progressLogger
-        1 * progressLogger.started()
-
-        when:
-        logger.afterSuite(rootSuite, result())
-
-        then:
-        1 * progressLogger.completed()
-    }
-
-    def logsCountOfTestsExecuted() {
-        TestDescriptor test1 = test()
-        TestDescriptor test2 = test()
-
-        1 * factory.newOperation(TestLogger) >> progressLogger
-        logger.beforeSuite(rootSuite)
-
-        when:
-        logger.afterTest(test1, result())
-
-        then:
-        1 * progressLogger.progress('1 test completed')
-
-        when:
-        logger.afterTest(test2, result())
-
-        then:
-        1 * progressLogger.progress('2 tests completed')
-
-        when:
-        logger.afterSuite(rootSuite, result())
-
-        then:
-        1 * progressLogger.completed()
-    }
-
-    def logsCountOfFailedTests() {
-        TestDescriptor test1 = test()
-        TestDescriptor test2 = test()
-
-        1 * factory.newOperation(TestLogger) >> progressLogger
-        logger.beforeSuite(rootSuite)
-
-        when:
-        logger.afterTest(test1, result())
-
-        then:
-        1 * progressLogger.progress('1 test completed')
-
-        when:
-        logger.afterTest(test2, result(true))
-
-        then:
-        1 * progressLogger.progress('2 tests completed, 1 failure')
-
-        when:
-        logger.afterSuite(rootSuite, result())
-
-        then:
-        1 * errorLogger.error('2 tests completed, 1 failure')
-        1 * progressLogger.completed()
-    }
-
-    def ignoresSuitesOtherThanTheRootSuite() {
-        TestDescriptor suite = suite()
-
-        1 * factory.newOperation(TestLogger) >> progressLogger
-        logger.beforeSuite(rootSuite)
-
-        when:
-        logger.beforeSuite(suite)
-        logger.afterSuite(suite, result())
-
-        then:
-        0 * progressLogger._
-
-        when:
-        logger.afterSuite(rootSuite, result())
-
-        then:
-        1 * progressLogger.completed()
-    }
-
-    private def test() {
-        [:] as TestDescriptor
-    }
-    
-    private def suite(boolean root = false) {
-        [getParent: {root ? null : [:] as TestDescriptor}] as TestDescriptor
-    }
-
-    private def result(boolean failed = false) {
-        [getTestCount: { 1L }, getFailedTestCount: { failed ? 1L : 0L}] as TestResult
-    }
-}
-
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy
deleted file mode 100755
index 9005e22..0000000
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2010 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 org.gradle.api.internal.tasks.testing.results
-
-import org.gradle.api.internal.tasks.testing.TestSuiteExecutionException
-import org.gradle.api.tasks.testing.TestDescriptor
-import org.gradle.api.tasks.testing.TestResult
-import org.slf4j.Logger
-import spock.lang.Specification
-import static org.junit.Assert.assertTrue
-
-public class TestSummaryListenerTest extends Specification {
-    
-    def logger = Mock(Logger.class)
-    def failure = new RuntimeException()
-    def listener = new TestSummaryListener(logger)
-
-    def "logs successful tests"() {
-        when:
-        listener.afterTest(test('<test>'), result(TestResult.ResultType.SUCCESS))
-        then:
-        1 * logger.info('{} PASSED', '<test>')
-        0 * logger._
-    }
-
-    def "logs skipped tests"() {
-        when:
-        listener.afterTest(test('<test>'), result(TestResult.ResultType.SKIPPED))
-        then:
-        1 * logger.info('{} SKIPPED', '<test>')
-        0 * logger._
-    }
-
-    def "logs failed test execution"() {
-        when:
-        listener.afterTest(test('<test>', '<class>'), result(TestResult.ResultType.FAILURE))
-        then:
-        1 * logger.info('{} FAILED: {}', '<test>', failure)
-        1 * logger.error('Test {} FAILED', '<class>')
-        0 * logger._
-    }
-
-    def "logs failed test execution when test has no class"() {
-        when:
-        listener.afterTest(test('<test>'), result(TestResult.ResultType.FAILURE))
-        then:
-        1 * logger.error('{} FAILED: {}', '<test>', failure)
-        0 * logger._
-    }
-
-    def "logs failed suite execution"() {
-        when:
-        listener.afterSuite(test('<test>', '<class>'), result(TestResult.ResultType.FAILURE))
-        then:
-        1 * logger.info('{} FAILED: {}', '<test>', failure)
-        1 * logger.error('Test {} FAILED', '<class>')
-        0 * logger._
-    }
-
-    def "logs Failed Suite Execution When Suite Has No Class"() {
-        when:
-        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE))
-        then:
-        1 * logger.error('{} FAILED: {}', '<test>', failure)
-        0 * logger._
-    }
-
-    def "logs Failed Suite Execution When Suite Has No Exception"() {
-        expect:
-        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE, null))
-    }
-
-    def "logs Suite Internal Exception"() {
-        given:
-        def failure = new TestSuiteExecutionException('broken', new RuntimeException())
-        when:
-        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE, failure))
-        then:
-        1 * logger.error('Execution for <test> FAILED', failure)
-        0 * logger._
-    }
-
-    def "does Not Log Failed Class More Than Once"() {
-        when:
-        listener.afterTest(test('<test1>', '<class>'), result(TestResult.ResultType.FAILURE))
-        listener.afterTest(test('<test2>', '<class>'), result(TestResult.ResultType.FAILURE))
-        listener.afterSuite(test('<test3>', '<class>'), result(TestResult.ResultType.FAILURE))
-        then:
-        1 * logger.error('Test {} FAILED', '<class>')
-    }
-
-    def "uses Root Suite Results To Determine If Tests Has Failed"() {
-        listener.afterSuite(test('<test>', null, null), result(TestResult.ResultType.FAILURE, null, 3, 5))
-        assertTrue(listener.hadFailures())
-    }
-
-    private TestResult result(TestResult.ResultType type, Throwable failure = this.failure, long failures = 0, long total = 0) {
-        return [getResultType: {-> type}, getException: {-> failure}, getTestCount: {-> total}, getFailedTestCount: {-> failures}] as TestResult
-    }
-
-    private TestDescriptor test(String name, String className = null, TestDescriptor parent = [:] as TestDescriptor) {
-        return [toString: {-> name}, getClassName: {-> className}, getParent: {-> parent}] as TestDescriptor
-    }
-}
-
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
index 2569caa..5c95aed 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
@@ -36,7 +36,7 @@ import org.testng.annotations.BeforeMethod
 import org.testng.annotations.Factory
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.gradle.util.LongIdGenerator
+import org.gradle.internal.id.LongIdGenerator
 import org.junit.Ignore
 import org.gradle.api.internal.tasks.testing.TestCompleteEvent
 import org.gradle.api.internal.tasks.testing.TestStartEvent
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
index 0bfeb2d..df3dcde 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
@@ -17,11 +17,12 @@
 package org.gradle.api.internal.tasks.testing.testng;
 
 import org.gradle.api.JavaVersion;
+import org.gradle.internal.Factory;
+import org.gradle.internal.id.IdGenerator;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.tasks.testing.AbstractTestFrameworkTest;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.tasks.testing.testng.TestNGOptions;
-import org.gradle.util.IdGenerator;
 import org.jmock.Expectations;
 import org.junit.Before;
 
@@ -47,6 +48,11 @@ public class TestNGTestFrameworkTest extends AbstractTestFrameworkTest {
         testngOptionsMock = context.mock(TestNGOptions.class);
         idGeneratorMock = context.mock(IdGenerator.class);
         serviceRegistry = context.mock(ServiceRegistry.class);
+        final Factory<File> temporaryDirFactory = new Factory<File>() {
+            public File create() {
+                return temporaryDir;
+            }
+        };
 
         final JavaVersion sourceCompatibility = JavaVersion.VERSION_1_5;
         context.checking(new Expectations() {{
@@ -55,6 +61,7 @@ public class TestNGTestFrameworkTest extends AbstractTestFrameworkTest {
             allowing(testMock).getTestClassesDir(); will(returnValue(testClassesDir));
             allowing(testMock).getClasspath(); will(returnValue(classpathMock));
             allowing(testMock).getTemporaryDir(); will(returnValue(temporaryDir));
+            allowing(testMock).getTemporaryDirFactory(); will(returnValue(temporaryDirFactory));
         }});
     }
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
index 97038a9..586787f 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
@@ -21,8 +21,8 @@ package org.gradle.api.reporting.internal
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.internal.AsmBackedClassGenerator
 import org.gradle.api.internal.ClassGeneratorBackedInstantiator
-import org.gradle.api.internal.DirectInstantiator
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.file.IdentityFileResolver
 import org.gradle.api.reporting.Report
 import org.gradle.api.reporting.ReportContainer
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy
index 399b41e..dcede8d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/reporting/internal/TaskReportContainerTest.groovy
@@ -19,7 +19,7 @@ package org.gradle.api.reporting.internal
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.Task
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.reporting.Report
 import org.gradle.api.tasks.Nested
 import org.gradle.testfixtures.ProjectBuilder
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
index f409317..8a358d6 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
@@ -40,6 +40,7 @@ class GroovyCompileOptionsTest {
         assertFalse(compileOptions.listFiles)
         assertFalse(compileOptions.verbose)
         assertTrue(compileOptions.fork)
+        assertEquals(['java', 'groovy'], compileOptions.fileExtensions)
         assertEquals('UTF-8', compileOptions.encoding)
         assertNotNull(compileOptions.forkOptions)
     }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
index 497dc68..3a7c5dd 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
@@ -22,14 +22,19 @@ import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.ConventionTask;
 import org.gradle.api.internal.file.CompositeFileTree;
 import org.gradle.api.internal.file.collections.DefaultFileCollectionResolveContext;
-import org.gradle.api.internal.file.collections.SimpleFileCollection;
 import org.gradle.api.internal.file.collections.DirectoryFileTree;
 import org.gradle.api.internal.file.collections.FileTreeAdapter;
+import org.gradle.api.internal.file.collections.SimpleFileCollection;
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
 import org.gradle.api.internal.tasks.testing.TestFramework;
+import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
 import org.gradle.api.internal.tasks.testing.detection.TestExecuter;
+import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
 import org.gradle.api.internal.tasks.testing.junit.JUnitTestFramework;
 import org.gradle.api.internal.tasks.testing.results.TestListenerAdapter;
 import org.gradle.api.tasks.AbstractConventionTaskTest;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.logging.internal.OutputEventListener;
 import org.gradle.util.GFileUtils;
 import org.gradle.util.HelperUtil;
 import org.gradle.util.TestClosure;
@@ -44,6 +49,7 @@ import org.junit.Before;
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.lang.ref.WeakReference;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -74,6 +80,7 @@ public class TestTest extends AbstractConventionTaskTest {
 
     TestFramework testFrameworkMock = context.mock(TestFramework.class);
     TestExecuter testExecuterMock = context.mock(TestExecuter.class);
+    OutputEventListener outputListenerMock = context.mock(OutputEventListener.class);
     private FileCollection classpathMock = new SimpleFileCollection(new File("classpath"));
     private Test test;
 
@@ -146,6 +153,46 @@ public class TestTest extends AbstractConventionTaskTest {
     }
 
     @org.junit.Test
+    public void testSetsTestFrameworkToNullAfterExecution() {
+        configureTask();
+        // using a jmock generated mock for testFramework does not work here as it is referenced
+        // by jmock holds some references.
+
+        test.useTestFramework(new TestFramework() {
+
+            public TestFrameworkDetector getDetector() {
+                return null;
+            }
+
+            public void report() {
+            }
+
+            public TestFrameworkOptions getOptions() {
+                return null;
+            }
+
+            public WorkerTestClassProcessorFactory getProcessorFactory() {
+                return null;
+            }
+
+            public org.gradle.api.Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
+                return null;
+            }
+        });
+        context.checking(new Expectations() {{
+            one(testExecuterMock).execute(with(sameInstance(test)), with(notNullValue(TestListenerAdapter.class)));
+        }});
+
+        WeakReference<TestFramework> weakRef = new WeakReference<TestFramework>(test.getTestFramework());
+        test.executeTests();
+
+        System.gc(); //explicit gc should normally be avoided, but necessary here.
+
+        assertNull(weakRef.get());
+
+    }
+
+    @org.junit.Test
     public void testDisablesParallelExecutionWhenInDebugMode() {
         configureTask();
         test.setDebug(true);
@@ -281,11 +328,13 @@ public class TestTest extends AbstractConventionTaskTest {
             will(returnValue(TestResult.ResultType.FAILURE));
             ignoring(result);
 
-            final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
+            final TestDescriptorInternal testDescriptor = context.mock(TestDescriptorInternal.class);
             allowing(testDescriptor).getName();
             will(returnValue("test"));
             allowing(testDescriptor).getParent();
             will(returnValue(null));
+            allowing(testDescriptor).getId();
+            will(returnValue(0));
 
             ignoring(testDescriptor);
 
@@ -309,7 +358,7 @@ public class TestTest extends AbstractConventionTaskTest {
     private void configureTask() {
         test.useTestFramework(testFrameworkMock);
         test.setTestExecuter(testExecuterMock);
-        
+
         test.setTestClassesDir(classesDir);
         test.setTestResultsDir(resultsDir);
         test.setTestReportDir(reportDir);
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy
new file mode 100644
index 0000000..a9927aa
--- /dev/null
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/environment/JreJavaHomeScalaIntegrationTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2012 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 org.gradle.scala.environment
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.util.Requires
+import org.gradle.util.TestPrecondition
+import spock.lang.IgnoreIf
+import spock.lang.Unroll
+
+class JreJavaHomeScalaIntegrationTest extends AbstractIntegrationSpec {
+
+
+    @IgnoreIf({ AvailableJavaHomes.bestJreAlternative == null})
+    @Unroll
+    def "scala java cross compilation works in forking mode = #forkMode when JAVA_HOME is set to JRE"() {
+        given:
+        def jreJavaHome = AvailableJavaHomes.bestJreAlternative
+        file("src/main/scala/org/test/JavaClazz.java") << """
+                    package org.test;
+                    public class JavaClazz {
+                        public static void main(String... args){
+
+                        }
+                    }
+                    """
+        writeScalaTestSource("src/main/scala")
+        file('build.gradle') << """
+                    println "Used JRE: ${jreJavaHome.absolutePath.replace(File.separator, '/')}"
+                    apply plugin:'scala'
+
+                    repositories {
+                        mavenCentral()
+                    }
+
+                    dependencies {
+                        scalaTools 'org.scala-lang:scala-compiler:2.8.1'
+                        scalaTools 'org.scala-lang:scala-library:2.8.1'
+                        compile    'org.scala-lang:scala-library:2.8.1'
+                    }
+
+                    compileScala{
+                        options.fork = ${forkMode}
+                    }
+                    """
+        when:
+        executer.withEnvironmentVars("JAVA_HOME": jreJavaHome.absolutePath).withTasks("compileScala").run().output
+        then:
+        file("build/classes/main/org/test/JavaClazz.class").exists()
+        file("build/classes/main/org/test/ScalaClazz.class").exists()
+
+        where:
+        forkMode << [false, true]
+    }
+
+    @Requires(TestPrecondition.WINDOWS)
+    def "scala compilation works when gradle is started with no java_home defined"() {
+        given:
+        writeScalaTestSource("src/main/scala");
+        file('build.gradle') << """
+                    apply plugin:'scala'
+
+                    repositories {
+                        mavenCentral()
+                    }
+
+                    dependencies {
+                        scalaTools 'org.scala-lang:scala-compiler:2.8.1'
+                        scalaTools 'org.scala-lang:scala-library:2.8.1'
+                        compile    'org.scala-lang:scala-library:2.8.1'
+                    }
+                    """
+        def envVars = System.getenv().findAll { it.key != 'JAVA_HOME' || it.key != 'Path'}
+        envVars.put("Path", "C:\\Windows\\System32")
+        when:
+        executer.withEnvironmentVars(envVars).withTasks("compileScala").run()
+        then:
+        file("build/classes/main/org/test/ScalaClazz.class").exists()
+    }
+
+    private writeScalaTestSource(String srcDir) {
+        file(srcDir, 'org/test/ScalaClazz.scala') << """
+        package org.test{
+            object ScalaClazz {
+                def main(args: Array[String]) {
+                    println("Hello, world!")
+                }
+            }
+        }
+        """
+    }
+}
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy
index 61e23e5..265592b 100644
--- a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningExtension.groovy
@@ -32,7 +32,7 @@ import org.gradle.plugins.signing.signatory.pgp.PgpSignatoryProvider
 import java.util.concurrent.Callable
 import org.gradle.api.artifacts.maven.MavenDeployment
 import org.gradle.util.ConfigureUtil
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 
 /**
  * The global signing configuration for a project.
diff --git a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPlugin.groovy b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPlugin.groovy
index c4cd3e5..c6a2dc9 100644
--- a/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPlugin.groovy
+++ b/subprojects/signing/src/main/groovy/org/gradle/plugins/signing/SigningPlugin.groovy
@@ -21,7 +21,7 @@ import org.gradle.api.Project
 import org.gradle.api.plugins.BasePlugin
 
 /**
- * Adds the ability to digitially sign files and artifacts.
+ * Adds the ability to digitally sign files and artifacts.
  */
 class SigningPlugin implements Plugin<Project> {
 
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy
index 5b283b2..d326bd7 100644
--- a/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/plugins/sonar/SonarPlugin.groovy
@@ -17,7 +17,7 @@ package org.gradle.api.plugins.sonar
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.internal.Instantiator
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.plugins.JavaBasePlugin
 import org.gradle.api.plugins.JavaPlugin
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
index e67b10a..609cee0 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/AutoTestedSamplesToolingApiTest.groovy
@@ -16,19 +16,19 @@
 
 package org.gradle.integtests.tooling
 
+import org.gradle.api.JavaVersion
 import org.gradle.integtests.fixtures.AutoTestedSamplesUtil
-import org.gradle.internal.jvm.Jvm
+import org.gradle.tooling.model.Element
+import org.gradle.util.ClasspathUtil
 import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import spock.lang.IgnoreIf
 import spock.lang.Specification
-import org.gradle.util.ClasspathUtil
-import org.gradle.tooling.model.Element
 
 /**
  * by Szczepan Faber, created at: 1/5/12
  */
- at IgnoreIf({!Jvm.current().java6Compatible})
+ at IgnoreIf({!JavaVersion.current().java6Compatible})
 public class AutoTestedSamplesToolingApiTest extends Specification {
 
     @Rule public final TemporaryFolder temp = new TemporaryFolder()
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
index 60be5d0..9c798b2 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/ConcurrentToolingApiIntegrationSpec.groovy
@@ -32,6 +32,8 @@ import org.gradle.tooling.model.idea.IdeaProject
 import org.junit.Rule
 import spock.lang.Issue
 import spock.lang.Specification
+import org.gradle.internal.classpath.ClassPath
+import java.util.concurrent.CopyOnWriteArrayList
 
 @Issue("GRADLE-1933")
 class ConcurrentToolingApiIntegrationSpec extends Specification {
@@ -58,7 +60,7 @@ class ConcurrentToolingApiIntegrationSpec extends Specification {
 
         when:
         threads.times {
-            concurrent.start { useToolingApi(dist) }
+            concurrent.start { useToolingApi(new GradleDistribution()) }
         }
 
         then:
@@ -177,7 +179,7 @@ project.description = text
         dist.file("build2/build.gradle") << "task foo2"
 
         when:
-        def allProgress = []
+        def allProgress = new CopyOnWriteArrayList<String>()
 
         concurrent.start {
             def connector = toolingApi.connector()
@@ -268,7 +270,7 @@ project.description = text
             return 'mock'
         }
 
-        Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+        ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
             def o = progressLoggerFactory.newOperation("mock")
             operation(o)
             o.started()
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
index 90419a0..7796351 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/SamplesToolingApiIntegrationTest.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.integtests.tooling
 
+import org.gradle.util.TextUtil
 import org.junit.Rule
 import spock.lang.Specification
 import org.gradle.integtests.fixtures.*
@@ -24,18 +25,11 @@ class SamplesToolingApiIntegrationTest extends Specification {
     @Rule public final Sample sample = new Sample()
 
     @UsesSample('toolingApi/eclipse')
-    def canUseToolingApiToDetermineProjectClasspath() {
-        def projectDir = sample.dir
-        Properties props = new Properties()
-        props['toolingApiRepo'] = distribution.libsRepo.toURI().toString()
-        props['gradleDistribution'] = distribution.gradleHomeDir.toString()
-        projectDir.file('gradle.properties').withOutputStream {outstr ->
-            props.store(outstr, 'props')
-        }
-        projectDir.file('settings.gradle').text = '// to stop search upwards'
+    def "can use tooling API to build Eclipse model"() {
+        tweakProject()
 
         when:
-        def result = run(projectDir)
+        def result = run()
 
         then:
         result.output.contains("gradle-tooling-api-")
@@ -43,45 +37,72 @@ class SamplesToolingApiIntegrationTest extends Specification {
     }
 
     @UsesSample('toolingApi/build')
-    def canUseToolingApiToRunABuild() {
-        def projectDir = sample.dir
-        Properties props = new Properties()
-        props['toolingApiRepo'] = distribution.libsRepo.toURI().toString()
-        props['gradleDistribution'] = distribution.gradleHomeDir.toString()
-        projectDir.file('gradle.properties').withOutputStream {outstr ->
-            props.store(outstr, 'props')
-        }
-        projectDir.file('settings.gradle').text = '// to stop search upwards'
+    def "can use tooling API to run tasks"() {
+        tweakProject()
 
         when:
-        def result = run(projectDir)
+        def result = run()
 
         then:
         result.output.contains("Welcome to Gradle")
     }
 
     @UsesSample('toolingApi/idea')
-    def buildsIdeaModel() {
+    def "can use tooling API to build IDEA model"() {
+        tweakProject()
+
+        when:
+        run()
+
+        then:
+        noExceptionThrown()
+    }
+
+    @UsesSample('toolingApi/model')
+    def "can use tooling API to build general model"() {
+        tweakProject()
+
+        when:
+        def result = run()
+
+        then:
+        result.output.contains("Project: model")
+        result.output.contains("    build")
+    }
+
+    private void tweakProject() {
         def projectDir = sample.dir
+
+        // Inject some additional configuration into the sample build script
+        def buildFile = projectDir.file('build.gradle')
+        def buildScript = buildFile.text
+        def index = buildScript.indexOf('repositories {')
+        assert index >= 0
+        buildScript = buildScript.substring(0, index) + """
+repositories {
+    maven { url "${distribution.libsRepo.toURI()}" }
+}
+run {
+    args = ["${TextUtil.escapeString(distribution.gradleHomeDir.absolutePath)}"]
+}
+""" + buildScript.substring(index)
+
+        buildFile.text = buildScript
+
+        // Tweak the build environment
         Properties props = new Properties()
-        props['toolingApiRepo'] = distribution.libsRepo.toURI().toString()
-        props['gradleDistribution'] = distribution.gradleHomeDir.toString()
+        props['org.gradle.daemon.idletimeout'] = '60000'
         projectDir.file('gradle.properties').withOutputStream {outstr ->
             props.store(outstr, 'props')
         }
-        projectDir.file('settings.gradle').text = '// to stop search upwards'
-
-        when:
-        run(projectDir)
 
-        then:
-        noExceptionThrown()
+        // Add in an empty settings file to avoid searching up
+        projectDir.file('settings.gradle').text = '// to stop search upwards'
     }
 
-    private ExecutionResult run(dir) {
+    private ExecutionResult run() {
         try {
-            return new GradleDistributionExecuter(distribution).inDirectory(dir)
-                    .withArguments("-PautomationSystemProperty=org.gradle.daemon.idletimeout=60000")
+            return new GradleDistributionExecuter(distribution).inDirectory(sample.dir)
                     .withTasks('run')
                     .run()
         } catch (Exception e) {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy
index 0d01704..6168b14 100755
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy
@@ -111,7 +111,7 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner
                 return GradleVersion.current()
             }
             if ("current".equals(annotation.value())) {
-                //so that one can use 'current' literal in the annotatin value
+                //so that one can use 'current' literal in the annotation value
                 //(useful if you don't know if the feature makes its way to the upcoming release)
                 return GradleVersion.current()
             }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
index 3d2b9b6..baeea57 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ToolingApiDistributionResolver.groovy
@@ -63,7 +63,9 @@ class ToolingApiDistributionResolver {
 
     private DependencyResolutionServices createResolutionServices() {
         GlobalServicesRegistry globalRegistry = new GlobalServicesRegistry()
-        TopLevelBuildServiceRegistry topLevelRegistry = new TopLevelBuildServiceRegistry(globalRegistry, new StartParameter())
+        StartParameter startParameter = new StartParameter()
+        startParameter.gradleUserHomeDir = currentGradleDistribution.userHomeDir
+        TopLevelBuildServiceRegistry topLevelRegistry = new TopLevelBuildServiceRegistry(globalRegistry, startParameter)
         ProjectInternalServiceRegistry projectRegistry = new ProjectInternalServiceRegistry(topLevelRegistry, HelperUtil.createRootProject())
         projectRegistry.get(DependencyResolutionServices)
     }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
new file mode 100644
index 0000000..84e07c1
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2009 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 org.gradle.integtests.tooling.r10rc1
+
+import org.gradle.integtests.tooling.fixture.ConfigurableOperation
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.ProjectConnection
+import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException
+import org.gradle.tooling.model.GradleProject
+
+ at MinToolingApiVersion("1.0-rc-1")
+ at MinTargetGradleVersion("1.0-rc-2")
+class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecification {
+
+//    We don't want to validate *all* command line options here, just enough to make sure passing through works.
+
+    def "understands project properties for building model"() {
+        given:
+        toolingApi.verboseLogging = false //sanity check, see GRADLE-2226
+        dist.file("build.gradle") << """
+        description = project.getProperty('theDescription')
+"""
+
+        when:
+        GradleProject project = withConnection { ProjectConnection it ->
+            it.model(GradleProject).withArguments('-PtheDescription=heyJoe').get()
+        }
+
+        then:
+        project.description == 'heyJoe'
+    }
+
+    def "understands system properties"() {
+        given:
+        dist.file("build.gradle") << """
+        task printProperty << {
+            file('sysProperty.txt') << System.getProperty('sysProperty')
+        }
+"""
+
+        when:
+        withConnection { ProjectConnection it ->
+            it.newBuild().forTasks('printProperty').withArguments('-DsysProperty=welcomeToTheJungle').run()
+        }
+
+        then:
+        dist.file('sysProperty.txt').text.contains('welcomeToTheJungle')
+    }
+
+    def "can use custom build file"() {
+        given:
+        dist.file("foo.gradle") << """
+        task someCoolTask
+"""
+
+        when:
+        withConnection { ProjectConnection it ->
+            it.newBuild().forTasks('someCoolTask').withArguments('-b', 'foo.gradle').run()
+        }
+
+        then:
+        noExceptionThrown()
+
+    }
+
+    def "can use custom log level"() {
+        //logging infrastructure is not installed when running in-process to avoid issues
+        toolingApi.isEmbedded = false
+
+        given:
+        dist.file("build.gradle") << """
+        logger.debug("debugging stuff")
+        logger.info("infoing stuff")
+"""
+
+        when:
+        def debug = withConnection {
+            def build = it.newBuild().withArguments('-d')
+            def op = new ConfigurableOperation(build)
+            build.run()
+            op.standardOutput
+        }
+
+        and:
+        def info = withConnection {
+            def build = it.newBuild().withArguments('-i')
+            def op = new ConfigurableOperation(build)
+            build.run()
+            op.standardOutput
+        }
+
+        then:
+        debug.count("debugging stuff") == 1
+        debug.count("infoing stuff") == 1
+
+        and:
+        info.count("debugging stuff") == 0
+        info.count("infoing stuff") == 1
+    }
+
+    def "gives decent feedback for invalid option"() {
+        when:
+        def ex = maybeFailWithConnection { ProjectConnection it ->
+            it.newBuild().withArguments('--foreground').run()
+        }
+
+        then:
+        ex instanceof UnsupportedBuildArgumentException
+        ex.message.contains('--foreground')
+    }
+
+    def "can overwrite project dir via build arguments"() {
+        given:
+        dist.file('otherDir').createDir()
+        dist.file('build.gradle') << "assert projectDir.name.endsWith('otherDir')"
+
+        when:
+        withConnection { 
+            it.newBuild().withArguments('-p', 'otherDir').run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can overwrite gradle user home via build arguments"() {
+        given:
+        dist.file('.myGradle').createDir()
+        dist.file('build.gradle') << "assert gradle.gradleUserHomeDir.name.endsWith('.myGradle')"
+
+        when:
+        withConnection {
+            it.newBuild().withArguments('-p', '.myGradle').run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can overwrite searchUpwards via build arguments"() {
+        given:
+        dist.file('build.gradle') << "assert !gradle.startParameter.searchUpwards"
+
+        when:
+        toolingApi.withConnector { it.searchUpwards(true) }
+        withConnection {
+            it.newBuild().withArguments('-u').run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+
+    def "can overwrite task names via build arguments"() {
+        given:
+        dist.file('build.gradle') << """
+task foo << { assert false }
+task bar << { assert true }
+"""
+
+        when:
+        withConnection {
+            it.newBuild().forTasks('foo').withArguments('bar').run()
+        }
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy
new file mode 100644
index 0000000..253bf03
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/DependencyMetaDataCrossVersionSpec.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.tooling.r11rc1
+
+import org.gradle.integtests.fixtures.MavenRepository
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.ExternalDependency
+import org.gradle.tooling.model.eclipse.EclipseProject
+import org.gradle.tooling.model.idea.IdeaProject
+
+ at MinToolingApiVersion('current')
+ at MinTargetGradleVersion('current')
+class DependencyMetaDataCrossVersionSpec extends ToolingApiSpecification {
+
+    def "idea libraries contain gradle module information"() {
+        given:
+        prepareBuild()
+
+        when:
+        IdeaProject project = withConnection { connection -> connection.getModel(IdeaProject.class) }
+        def module = project.modules[0]
+        def libs = module.dependencies
+
+        then:
+        containModuleInfo(libs)
+    }
+
+    def "eclipse libraries contain gradle module information"() {
+        given:
+        prepareBuild()
+
+        when:
+        EclipseProject project = withConnection { connection -> connection.getModel(EclipseProject.class) }
+        def libs = project.classpath
+
+        then:
+        containModuleInfo(libs)
+    }
+
+    private void prepareBuild() {
+        def fakeRepo = dist.file("repo")
+        new MavenRepository(fakeRepo).module("foo.bar", "coolLib", 2.0).publish()
+
+        dist.file("yetAnotherJar.jar").createFile()
+
+        dist.file('build.gradle').text = """
+apply plugin: 'java'
+
+repositories {
+    maven { url "${fakeRepo.toURI()}" }
+}
+
+dependencies {
+    compile 'foo.bar:coolLib:2.0'
+    compile 'unresolved.org:funLib:1.0'
+    compile files('yetAnotherJar.jar')
+}
+"""
+    }
+
+    private void containModuleInfo(libs) {
+        assert libs.size() == 3
+
+        ExternalDependency coolLib = libs.find { it.file.name == 'coolLib-2.0.jar' }
+        assert coolLib.gradleModuleVersion
+        assert coolLib.gradleModuleVersion.group == 'foo.bar'
+        assert coolLib.gradleModuleVersion.name == 'coolLib'
+        assert coolLib.gradleModuleVersion.version == '2.0'
+
+        ExternalDependency funLib = libs.find { it.file.name.contains('funLib') }
+        assert funLib.gradleModuleVersion == null
+
+        ExternalDependency yetAnotherJar = libs.find { it.file.name == 'yetAnotherJar.jar' }
+        assert yetAnotherJar.gradleModuleVersion == null
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec.groovy
new file mode 100644
index 0000000..b9dac68
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 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 org.gradle.integtests.tooling.r11rc1
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.tooling.model.internal.migration.ProjectOutput
+import org.gradle.tooling.internal.migration.DefaultArchive
+import org.gradle.tooling.internal.migration.DefaultTestResult
+
+import org.junit.Rule
+
+ at MinToolingApiVersion("current")
+ at MinTargetGradleVersion("current")
+class MigrationModelCrossVersionSpec extends ToolingApiSpecification {
+    @Rule TestResources resources = new TestResources()
+
+    def "modelContainsAllArchivesOnTheArchivesConfiguration"() {
+        when:
+        def output = withConnection { it.getModel(ProjectOutput.class) }
+
+        then:
+        output instanceof ProjectOutput
+        def archives = output.taskOutputs.findAll { it.getClass().name == DefaultArchive.name } as List
+        archives.size() == 2
+        archives.any { it.file.name.endsWith(".jar") }
+        archives.any { it.file.name.endsWith(".zip") }
+    }
+
+    def "modelContainsAllTestResults"() {
+        when:
+        def output = withConnection { it.getModel(ProjectOutput.class) }
+
+        then:
+        output instanceof ProjectOutput
+        def testResults = output.taskOutputs.findAll { it.getClass().name == DefaultTestResult.name } as List
+        testResults.size() == 2
+        testResults.any { it.xmlReportDir == resources.dir.file("build", "test-results") }
+        testResults.any { it.xmlReportDir == resources.dir.file("build", "other-results") }
+    }
+
+    def "modelContainsAllProjects"() {
+        when:
+        def output = withConnection { it.getModel(ProjectOutput.class) }
+
+        then:
+        output instanceof ProjectOutput
+        output.children.size() == 2
+        output.children.name as Set == ["project1", "project2"] as Set
+        output.children[0].children.empty
+        output.children[1].children.empty
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
deleted file mode 100644
index bfbda07..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2009 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 org.gradle.integtests.tooling.rc1
-
-import org.gradle.integtests.tooling.fixture.ConfigurableOperation
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
-import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
-import org.gradle.tooling.ProjectConnection
-import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException
-import org.gradle.tooling.model.GradleProject
-
- at MinToolingApiVersion("1.0-rc-1")
- at MinTargetGradleVersion("1.0-rc-2")
-class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecification {
-
-//    We don't want to validate *all* command line options here, just enough to make sure passing through works.
-
-    def "understands project properties for building model"() {
-        given:
-        toolingApi.verboseLogging = false //sanity check, see GRADLE-2226
-        dist.file("build.gradle") << """
-        description = project.getProperty('theDescription')
-"""
-
-        when:
-        GradleProject project = withConnection { ProjectConnection it ->
-            it.model(GradleProject).withArguments('-PtheDescription=heyJoe').get()
-        }
-
-        then:
-        project.description == 'heyJoe'
-    }
-
-    def "understands system properties"() {
-        given:
-        dist.file("build.gradle") << """
-        task printProperty << {
-            file('sysProperty.txt') << System.getProperty('sysProperty')
-        }
-"""
-
-        when:
-        withConnection { ProjectConnection it ->
-            it.newBuild().forTasks('printProperty').withArguments('-DsysProperty=welcomeToTheJungle').run()
-        }
-
-        then:
-        dist.file('sysProperty.txt').text.contains('welcomeToTheJungle')
-    }
-
-    def "can use custom build file"() {
-        given:
-        dist.file("foo.gradle") << """
-        task someCoolTask
-"""
-
-        when:
-        withConnection { ProjectConnection it ->
-            it.newBuild().forTasks('someCoolTask').withArguments('-b', 'foo.gradle').run()
-        }
-
-        then:
-        noExceptionThrown()
-
-    }
-
-    def "can use custom log level"() {
-        //logging infrastructure is not installed when running in-process to avoid issues
-        toolingApi.isEmbedded = false
-
-        given:
-        dist.file("build.gradle") << """
-        logger.debug("debugging stuff")
-        logger.info("infoing stuff")
-"""
-
-        when:
-        def debug = withConnection {
-            def build = it.newBuild().withArguments('-d')
-            def op = new ConfigurableOperation(build)
-            build.run()
-            op.standardOutput
-        }
-
-        and:
-        def info = withConnection {
-            def build = it.newBuild().withArguments('-i')
-            def op = new ConfigurableOperation(build)
-            build.run()
-            op.standardOutput
-        }
-
-        then:
-        debug.count("debugging stuff") == 1
-        debug.count("infoing stuff") == 1
-
-        and:
-        info.count("debugging stuff") == 0
-        info.count("infoing stuff") == 1
-    }
-
-    def "gives decent feedback for invalid option"() {
-        when:
-        def ex = maybeFailWithConnection { ProjectConnection it ->
-            it.newBuild().withArguments('--foreground').run()
-        }
-
-        then:
-        ex instanceof UnsupportedBuildArgumentException
-        ex.message.contains('--foreground')
-    }
-
-    def "can overwrite project dir via build arguments"() {
-        given:
-        dist.file('otherDir').createDir()
-        dist.file('build.gradle') << "assert projectDir.name.endsWith('otherDir')"
-
-        when:
-        withConnection { 
-            it.newBuild().withArguments('-p', 'otherDir').run()
-        }
-
-        then:
-        noExceptionThrown()
-    }
-
-    def "can overwrite gradle user home via build arguments"() {
-        given:
-        dist.file('.myGradle').createDir()
-        dist.file('build.gradle') << "assert gradle.gradleUserHomeDir.name.endsWith('.myGradle')"
-
-        when:
-        withConnection {
-            it.newBuild().withArguments('-p', '.myGradle').run()
-        }
-
-        then:
-        noExceptionThrown()
-    }
-
-    def "can overwrite searchUpwards via build arguments"() {
-        given:
-        dist.file('build.gradle') << "assert !gradle.startParameter.searchUpwards"
-
-        when:
-        toolingApi.withConnector { it.searchUpwards(true) }
-        withConnection {
-            it.newBuild().withArguments('-u').run()
-        }
-
-        then:
-        noExceptionThrown()
-    }
-
-    def "can overwrite task names via build arguments"() {
-        given:
-        dist.file('build.gradle') << """
-task foo << { assert false }
-task bar << { assert true }
-"""
-
-        when:
-        withConnection {
-            it.newBuild().forTasks('foo').withArguments('bar').run()
-        }
-
-        then:
-        noExceptionThrown()
-    }
-}
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/build.gradle b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/build.gradle
new file mode 100644
index 0000000..ed64449
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "java"
+
+task zip(type: Zip) {
+    from "file.txt"
+}
+
+artifacts {
+    archives zip
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/file.txt b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/file.txt
new file mode 100644
index 0000000..6b584e8
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/file.txt
@@ -0,0 +1 @@
+content
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/src/main/java/Person.java b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/src/main/java/Person.java
new file mode 100644
index 0000000..cb98f64
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllArchivesOnTheArchivesConfiguration/src/main/java/Person.java
@@ -0,0 +1,3 @@
+public class Person {
+    String name;
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/build.gradle b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/build.gradle
new file mode 100644
index 0000000..db76069
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/build.gradle
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2012 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: "java"
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/settings.gradle b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/settings.gradle
new file mode 100644
index 0000000..f479356
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllProjects/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2012 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.
+ */
+
+include "project1", "project2"
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllTestResults/build.gradle b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllTestResults/build.gradle
new file mode 100644
index 0000000..25222a3
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/resources/org/gradle/integtests/tooling/r11rc1/MigrationModelCrossVersionSpec/modelContainsAllTestResults/build.gradle
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 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: "java"
+
+task integTest(type: Test) {
+    testResultsDir = file("$buildDir/other-results")
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java
index 8d43b5d..41eeb3e 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/BuildLauncher.java
@@ -31,7 +31,7 @@ import java.io.OutputStream;
  * <li>Create an instance of {@code BuildLauncher} by calling {@link org.gradle.tooling.ProjectConnection#newBuild()}.
  * <li>Configure the launcher as appropriate.
  * <li>Call either {@link #run()} or {@link #run(ResultHandler)} to execute the build.
- * <li>Optionally, you can reuse the launcher to launcher additional builds.
+ * <li>Optionally, you can reuse the launcher to launch additional builds.
  * </ul>
  *
  * Example:
@@ -131,7 +131,7 @@ public interface BuildLauncher extends LongRunningOperation {
     BuildLauncher addProgressListener(ProgressListener listener);
 
     /**
-     * Execute the build, blocking until it is complete.
+     * Executes the build, blocking until it is complete.
      *
      * @throws UnsupportedVersionException When the target Gradle version does not support the features required for this build.
      * @throws org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
@@ -147,7 +147,7 @@ public interface BuildLauncher extends LongRunningOperation {
             BuildException, UnsupportedVersionException;
 
     /**
-     * Launchers the build. This method returns immediately, and the result is later passed to the given handler.
+     * Launches the build. This method returns immediately, and the result is later passed to the given handler.
      *
      * @param handler The handler to supply the result to.
      * @throws IllegalStateException When the connection has been closed or is closing.
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java
index 456e217..787f844 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/LongRunningOperation.java
@@ -21,16 +21,16 @@ import java.io.InputStream;
 import java.io.OutputStream;
 
 /**
- * Offers ways to communicate both ways with a gradle operation, be it building a model or running tasks.
+ * Offers ways to communicate both ways with a Gradle operation, be it building a model or running tasks.
  * <p>
- * Enables tracking progress via listeners that will receive events from the gradle operation.
+ * Enables tracking progress via listeners that will receive events from the Gradle operation.
  * <p>
- * Allows providing standard output streams that will receive output if the gradle operation writes to standard streams.
+ * Allows providing standard output streams that will receive output if the Gradle operation writes to standard streams.
  * <p>
  * Allows providing standard input that can be consumed by the gradle operation (useful for interactive builds).
  * <p>
- * Enables configuring the build run / model request with options like the java home or jvm arguments.
- * Those settings might not be supported by the target Gradle version. Refer to javadoc for those methods
+ * Enables configuring the build run / model request with options like the Java home or jvm arguments.
+ * Those settings might not be supported by the target Gradle version. Refer to Javadoc for those methods
  * to understand what kind of exception throw and when is it thrown.
  */
 public interface LongRunningOperation {
@@ -54,11 +54,11 @@ public interface LongRunningOperation {
     LongRunningOperation setStandardError(OutputStream outputStream);
 
     /**
-     * If the target gradle version supports it you can use this setting
+     * If the target Gradle version supports it you can use this setting
      * to set the standard {@link java.io.InputStream} that will be used by builds.
      * Useful when the tooling api drives interactive builds.
      * <p>
-     * If the target gradle version does not support it the long running operation will fail eagerly with
+     * If the target Gradle version does not support it the long running operation will fail eagerly with
      * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
      * <p>
      * If not configured or null passed the dummy input stream with zero bytes is used to avoid the build hanging problems.
@@ -70,18 +70,18 @@ public interface LongRunningOperation {
     LongRunningOperation setStandardInput(InputStream inputStream);
 
     /**
-     * If the target gradle version supports it you can use this setting
-     * to specify the java home directory to use for the long running operation.
+     * If the target Gradle version supports it you can use this setting
+     * to specify the Java home directory to use for the long running operation.
      * <p>
-     * If the target gradle version does not support it the long running operation will fail eagerly with
+     * If the target Gradle version does not support it the long running operation will fail eagerly with
      * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
      * <p>
-     * {@link org.gradle.tooling.model.build.BuildEnvironment} model contains information such as java or gradle environment.
+     * {@link org.gradle.tooling.model.build.BuildEnvironment} model contains information such as Java or Gradle environment.
      * If you want to get hold of this information you can ask tooling API to build this model.
      * <p>
      * If not configured or null passed the sensible default will be used.
      *
-     * @param javaHome to use for the gradle process
+     * @param javaHome to use for the Gradle process
      * @return this
      * @since 1.0-milestone-8
      * @throws IllegalArgumentException when supplied javaHome is not a valid folder.
@@ -89,18 +89,18 @@ public interface LongRunningOperation {
     LongRunningOperation setJavaHome(File javaHome) throws IllegalArgumentException;
 
     /**
-     * If the target gradle version supports it you can use this setting
-     * to specify the java vm arguments to use for the long running operation.
+     * If the target Gradle version supports it you can use this setting
+     * to specify the Java vm arguments to use for the long running operation.
      * <p>
-     * If the target gradle version does not support it the long running operation will fail eagerly with
+     * If the target Gradle version does not support it the long running operation will fail eagerly with
      * {@link org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException} when the operation is started.
      * <p>
-     * {@link org.gradle.tooling.model.build.BuildEnvironment} model contains information such as java or gradle environment.
+     * {@link org.gradle.tooling.model.build.BuildEnvironment} model contains information such as Java or Gradle environment.
      * If you want to get hold of this information you can ask tooling API to build this model.
      * <p>
      * If not configured, null an empty array passed then the reasonable default will be used.
      *
-     * @param jvmArguments to use for the gradle process
+     * @param jvmArguments to use for the Gradle process
      * @return this
      * @since 1.0-milestone-9
      */
@@ -131,7 +131,7 @@ public interface LongRunningOperation {
      * <p>
      * See the example in the docs for {@link BuildLauncher}
      *
-     * @param arguments gradle command line arguments
+     * @param arguments Gradle command line arguments
      * @return this
      * @since 1.0-rc-1
      */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedOperationConfigurationException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedOperationConfigurationException.java
index a6a2bd9..1989538 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedOperationConfigurationException.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/exceptions/UnsupportedOperationConfigurationException.java
@@ -19,9 +19,9 @@ package org.gradle.tooling.exceptions;
 import org.gradle.tooling.UnsupportedVersionException;
 
 /**
- * Thrown when the {@link org.gradle.tooling.LongRunningOperation} has been configured
+ * Thrown when a {@link org.gradle.tooling.LongRunningOperation} has been configured
  * with unsupported settings. For example {@link org.gradle.tooling.LongRunningOperation#setJavaHome(java.io.File)}
- * might not be supported by the target gradle version.
+ * might not be supported by the target Gradle version.
  *
  * @since 1.0-rc-1
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
index a60807d..c615983 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectionFactory.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.internal.concurrent.DefaultExecutorFactory;
 import org.gradle.tooling.ProjectConnection;
 import org.gradle.tooling.internal.consumer.async.AsyncConnection;
 import org.gradle.tooling.internal.consumer.async.DefaultAsyncConnection;
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java
index 0eefc17..be2c2e3 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/ConnectorServices.java
@@ -17,7 +17,7 @@
 package org.gradle.tooling.internal.consumer;
 
 import org.gradle.StartParameter;
-import org.gradle.api.internal.concurrent.SynchronizedServiceRegistry;
+import org.gradle.internal.service.SynchronizedServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.tooling.internal.consumer.loader.CachingToolingImplementationLoader;
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
index 2d0d179..e04002f 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/Distribution.java
@@ -15,13 +15,11 @@
  */
 package org.gradle.tooling.internal.consumer;
 
+import org.gradle.internal.classpath.ClassPath;
 import org.gradle.logging.ProgressLoggerFactory;
 
-import java.io.File;
-import java.util.Set;
-
 public interface Distribution {
     String getDisplayName();
 
-    Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory);
+    ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory);
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DistributionFactory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DistributionFactory.java
index 4d2873c..bf45a90 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DistributionFactory.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/DistributionFactory.java
@@ -15,9 +15,11 @@
  */
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.api.internal.classpath.DefaultModuleRegistry;
+import org.gradle.api.internal.classpath.EffectiveClassPath;
 import org.gradle.initialization.layout.BuildLayout;
 import org.gradle.initialization.layout.BuildLayoutFactory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.classpath.DefaultClassPath;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.tooling.GradleConnectionException;
@@ -98,7 +100,7 @@ public class DistributionFactory {
             return String.format("Gradle distribution '%s'", wrapperConfiguration.getDistribution());
         }
 
-        public Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
             if (installedDistribution == null) {
                 File installDir;
                 try {
@@ -149,7 +151,7 @@ public class DistributionFactory {
             return displayName;
         }
 
-        public Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
             ProgressLogger progressLogger = progressLoggerFactory.newOperation(DistributionFactory.class);
             progressLogger.setDescription("Validate distribution");
             progressLogger.started();
@@ -160,7 +162,7 @@ public class DistributionFactory {
             }
         }
 
-        private Set<File> getToolingImpl() {
+        private ClassPath getToolingImpl() {
             if (!gradleHomeDir.exists()) {
                 throw new IllegalArgumentException(String.format("The specified %s does not exist.", locationDisplayName));
             }
@@ -177,7 +179,7 @@ public class DistributionFactory {
                     files.add(file);
                 }
             }
-            return files;
+            return new DefaultClassPath(files);
         }
     }
 
@@ -186,8 +188,8 @@ public class DistributionFactory {
             return "Gradle classpath distribution";
         }
 
-        public Set<File> getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
-            return new DefaultModuleRegistry().getFullClasspath();
+        public ClassPath getToolingImplementationClasspath(ProgressLoggerFactory progressLoggerFactory) {
+            return new EffectiveClassPath(getClass().getClassLoader());
         }
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java
index bfa3654..6797a1d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/SynchronizedLogging.java
@@ -16,14 +16,13 @@
 
 package org.gradle.tooling.internal.consumer;
 
-import org.gradle.api.internal.Operation;
-import org.gradle.api.internal.concurrent.Synchronizer;
+import org.gradle.internal.concurrent.Synchronizer;
 import org.gradle.internal.Factory;
+import org.gradle.internal.TrueTimeProvider;
 import org.gradle.listener.DefaultListenerManager;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.internal.DefaultProgressLoggerFactory;
 import org.gradle.logging.internal.ProgressListener;
-import org.gradle.util.TrueTimeProvider;
 
 /**
  * Thread safe logging provider that needs to be initialized before use.
@@ -58,8 +57,8 @@ public class SynchronizedLogging implements LoggingProvider {
     }
 
     public void init() {
-        synchronizer.synchronize(new Operation() {
-            public void execute() {
+        synchronizer.synchronize(new Runnable() {
+            public void run() {
                 DefaultListenerManager manager = new DefaultListenerManager();
                 listenerManager.set(manager);
                 progressLoggerFactory.set(new DefaultProgressLoggerFactory(manager.getBroadcaster(ProgressListener.class), new TrueTimeProvider()));
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConnection.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConnection.java
index 4b53a37..6975409 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConnection.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/async/DefaultAsyncConnection.java
@@ -15,8 +15,8 @@
  */
 package org.gradle.tooling.internal.consumer.async;
 
-import org.gradle.messaging.concurrent.ExecutorFactory;
-import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.internal.concurrent.ExecutorFactory;
+import org.gradle.internal.concurrent.StoppableExecutor;
 import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
 import org.gradle.tooling.internal.consumer.parameters.ConsumerOperationParameters;
 import org.gradle.tooling.internal.consumer.versioning.VersionDetails;
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoader.java
index bf3fb1b..28d8100 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoader.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoader.java
@@ -15,26 +15,24 @@
  */
 package org.gradle.tooling.internal.consumer.loader;
 
+import org.gradle.internal.classpath.ClassPath;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.tooling.internal.consumer.Distribution;
 import org.gradle.tooling.internal.consumer.connection.ConsumerConnection;
 
-import java.io.File;
 import java.util.HashMap;
-import java.util.LinkedHashSet;
 import java.util.Map;
-import java.util.Set;
 
 public class CachingToolingImplementationLoader implements ToolingImplementationLoader {
     private final ToolingImplementationLoader loader;
-    private final Map<Set<File>, ConsumerConnection> connections = new HashMap<Set<File>, ConsumerConnection>();
+    private final Map<ClassPath, ConsumerConnection> connections = new HashMap<ClassPath, ConsumerConnection>();
 
     public CachingToolingImplementationLoader(ToolingImplementationLoader loader) {
         this.loader = loader;
     }
 
     public ConsumerConnection create(Distribution distribution, ProgressLoggerFactory progressLoggerFactory, boolean verboseLogging) {
-        Set<File> classpath = new LinkedHashSet<File>(distribution.getToolingImplementationClasspath(progressLoggerFactory));
+        ClassPath classpath = distribution.getToolingImplementationClasspath(progressLoggerFactory);
 
         ConsumerConnection connection = connections.get(classpath);
         if (connection == null) {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoader.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoader.java
index e559004..bdd1d6a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoader.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoader.java
@@ -16,6 +16,8 @@
 package org.gradle.tooling.internal.consumer.loader;
 
 import org.gradle.internal.Factory;
+import org.gradle.internal.classpath.ClassPath;
+import org.gradle.internal.service.ServiceLocator;
 import org.gradle.logging.ProgressLoggerFactory;
 import org.gradle.tooling.GradleConnectionException;
 import org.gradle.tooling.UnsupportedVersionException;
@@ -26,9 +28,6 @@ import org.gradle.util.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.net.URL;
-import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -70,11 +69,10 @@ public class DefaultToolingImplementationLoader implements ToolingImplementation
     }
 
     private ClassLoader createImplementationClassLoader(Distribution distribution, ProgressLoggerFactory progressLoggerFactory) {
-        Set<File> implementationClasspath = distribution.getToolingImplementationClasspath(progressLoggerFactory);
+        ClassPath implementationClasspath = distribution.getToolingImplementationClasspath(progressLoggerFactory);
         LOGGER.debug("Using tooling provider classpath: {}", implementationClasspath);
-        URL[] urls = GFileUtils.toURLArray(implementationClasspath);
         FilteringClassLoader filteringClassLoader = new FilteringClassLoader(classLoader);
         filteringClassLoader.allowPackage("org.gradle.tooling.internal.protocol");
-        return new MutableURLClassLoader(filteringClassLoader, urls);
+        return new MutableURLClassLoader(filteringClassLoader, implementationClasspath.getAsURLArray());
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java
index e69fc7c..46078bc 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/versioning/ModelMapping.java
@@ -26,6 +26,7 @@ import org.gradle.tooling.model.eclipse.HierarchicalEclipseProject;
 import org.gradle.tooling.model.idea.BasicIdeaProject;
 import org.gradle.tooling.model.idea.IdeaProject;
 import org.gradle.tooling.model.internal.TestModel;
+import org.gradle.tooling.model.internal.migration.ProjectOutput;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -56,6 +57,7 @@ public class ModelMapping {
         Map<Class<? extends Model>, Class> map = new HashMap<Class<? extends Model>, Class>();
         map.put(BuildEnvironment.class, InternalBuildEnvironment.class);
         map.put(TestModel.class, InternalTestModel.class);
+        map.put(ProjectOutput.class, InternalProjectOutput.class);
         return map;
     }
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java
index e8be323..0f1067c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/eclipse/DefaultEclipseExternalDependency.java
@@ -15,7 +15,10 @@
  */
 package org.gradle.tooling.internal.eclipse;
 
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.tooling.internal.gradle.DefaultGradleModuleVersion;
 import org.gradle.tooling.internal.protocol.ExternalDependencyVersion1;
+import org.gradle.tooling.model.GradleModuleVersion;
 
 import java.io.File;
 import java.io.Serializable;
@@ -24,11 +27,13 @@ public class DefaultEclipseExternalDependency implements ExternalDependencyVersi
     private final File file;
     private final File javadoc;
     private final File source;
+    private final GradleModuleVersion moduleVersion;
 
-    public DefaultEclipseExternalDependency(File file, File javadoc, File source) {
+    public DefaultEclipseExternalDependency(File file, File javadoc, File source, ModuleVersionIdentifier identifier) {
         this.file = file;
         this.javadoc = javadoc;
         this.source = source;
+        moduleVersion = (identifier == null)? null : new DefaultGradleModuleVersion(identifier);
     }
 
     public File getFile() {
@@ -42,4 +47,8 @@ public class DefaultEclipseExternalDependency implements ExternalDependencyVersi
     public File getSource() {
         return source;
     }
+
+    public GradleModuleVersion getGradleModuleVersion() {
+        return moduleVersion;
+    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleModuleVersion.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleModuleVersion.java
new file mode 100644
index 0000000..203bb56
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/gradle/DefaultGradleModuleVersion.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.internal.gradle;
+
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.tooling.model.GradleModuleVersion;
+
+import java.io.Serializable;
+
+/**
+ * by Szczepan Faber, created at: 5/11/12
+ */
+public class DefaultGradleModuleVersion implements GradleModuleVersion, Serializable {
+
+    private final String group;
+    private final String name;
+    private final String version;
+
+    public DefaultGradleModuleVersion(ModuleVersionIdentifier identifier) {
+        this.group = identifier.getGroup();
+        this.name = identifier.getName();
+        this.version = identifier.getVersion();
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public String toString() {
+        return "GradleModuleVersion{"
+                 + "group='" + group + '\''
+                 + ", name='" + name + '\''
+                 + ", version='" + version + '\''
+                 + '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java
index c97f967..8afa04c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaProject.java
@@ -16,7 +16,6 @@
 
 package org.gradle.tooling.internal.idea;
 
-import org.gradle.api.GradleException;
 import org.gradle.tooling.internal.protocol.InternalIdeaProject;
 import org.gradle.tooling.model.DomainObjectSet;
 import org.gradle.tooling.model.HierarchicalElement;
@@ -85,11 +84,11 @@ public class DefaultIdeaProject implements InternalIdeaProject, IdeaProject, Ser
     }
 
     public File getProjectDirectory() {
-        throw new GradleException("This method should not be used.");
+        throw new UnsupportedOperationException("This method should not be used.");
     }
 
     public String getPath() {
-        throw new GradleException("This method should not be used.");
+        throw new UnsupportedOperationException("This method should not be used.");
     }
 
     public DefaultIdeaProject setChildren(Collection<? extends IdeaModule> children) {
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
index d58d1ee..e774bea 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaSingleEntryLibraryDependency.java
@@ -16,6 +16,7 @@
 
 package org.gradle.tooling.internal.idea;
 
+import org.gradle.tooling.model.GradleModuleVersion;
 import org.gradle.tooling.model.idea.IdeaDependencyScope;
 import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency;
 
@@ -32,6 +33,7 @@ public class DefaultIdeaSingleEntryLibraryDependency implements IdeaSingleEntryL
     private File javadoc;
     private Boolean exported;
     private IdeaDependencyScope scope;
+    private GradleModuleVersion moduleVersion;
 
     public File getFile() {
         return file;
@@ -55,6 +57,10 @@ public class DefaultIdeaSingleEntryLibraryDependency implements IdeaSingleEntryL
         return javadoc;
     }
 
+    public GradleModuleVersion getGradleModuleVersion() {
+        return moduleVersion;
+    }
+
     public DefaultIdeaSingleEntryLibraryDependency setJavadoc(File javadoc) {
         this.javadoc = javadoc;
         return this;
@@ -78,6 +84,11 @@ public class DefaultIdeaSingleEntryLibraryDependency implements IdeaSingleEntryL
         return this;
     }
 
+    public DefaultIdeaSingleEntryLibraryDependency setExternalGradleModule(GradleModuleVersion moduleVersion) {
+        this.moduleVersion = moduleVersion;
+        return this;
+    }
+
     @Override
     public String toString() {
         return "IdeaLibraryDependency{"
@@ -86,6 +97,7 @@ public class DefaultIdeaSingleEntryLibraryDependency implements IdeaSingleEntryL
                 + ", javadoc=" + javadoc
                 + ", exported=" + exported
                 + ", scope='" + scope + '\''
+                + ", id='" + moduleVersion + '\''
                 + '}';
     }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultArchive.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultArchive.java
new file mode 100644
index 0000000..2e93086
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultArchive.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.internal.migration;
+
+import org.gradle.tooling.model.internal.migration.Archive;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultArchive implements Archive, Serializable {
+    private final File file;
+
+    public DefaultArchive(File file) {
+        this.file = file;
+    }
+
+    public File getFile() {
+        return file;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultProjectOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultProjectOutput.java
new file mode 100644
index 0000000..b3a1b55
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultProjectOutput.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.internal.migration;
+
+import com.google.common.collect.Lists;
+
+import org.gradle.tooling.internal.protocol.InternalProjectOutput;
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
+import org.gradle.tooling.model.internal.migration.ProjectOutput;
+import org.gradle.tooling.model.internal.migration.TaskOutput;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+public class DefaultProjectOutput implements InternalProjectOutput, ProjectOutput, Serializable {
+    private final String name;
+    private final String path;
+    private final String description;
+    private final File projectDirectory;
+    private final Set<TaskOutput> taskOutputs;
+    private final ProjectOutput parent;
+    private final List<ProjectOutput> children = Lists.newArrayList();
+
+    public DefaultProjectOutput(String name, String path, String description, File projectDirectory, Set<TaskOutput> taskOutputs, ProjectOutput parent) {
+        this.name = name;
+        this.path = path;
+        this.description = description;
+        this.projectDirectory = projectDirectory;
+        this.taskOutputs = taskOutputs;
+        this.parent = parent;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public File getProjectDirectory() {
+        return projectDirectory;
+    }
+
+    public Set<TaskOutput> getTaskOutputs() {
+        return taskOutputs;
+    }
+
+    public ProjectOutput getParent() {
+        return parent;
+    }
+
+    public DomainObjectSet<ProjectOutput> getChildren() {
+        return new ImmutableDomainObjectSet<ProjectOutput>(children);
+    }
+
+    public void addChild(ProjectOutput child) {
+        children.add(child);
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultTestResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultTestResult.java
new file mode 100644
index 0000000..b1f7ad0
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/migration/DefaultTestResult.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.internal.migration;
+
+import org.gradle.tooling.model.internal.migration.TestResult;
+
+import java.io.File;
+import java.io.Serializable;
+
+public class DefaultTestResult implements TestResult, Serializable {
+    private final File xmlReportDir;
+
+    public DefaultTestResult(File xmlReportDir) {
+        this.xmlReportDir = xmlReportDir;
+    }
+
+    public File getXmlReportDir() {
+        return xmlReportDir;
+    }
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProjectOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProjectOutput.java
new file mode 100644
index 0000000..79bf15b
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/protocol/InternalProjectOutput.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.internal.protocol;
+
+public interface InternalProjectOutput extends ProjectVersion3, InternalProtocolInterface {
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java
index 1390b91..e8bf402 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/BuildableElement.java
@@ -26,7 +26,7 @@ public interface BuildableElement extends Element {
     /**
      * Returns the tasks of this project.
      *
-     * @return The tasks.
+     * @return The tasks of this project.
      */
     DomainObjectSet<? extends Task> getTasks();
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/DomainObjectSet.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/DomainObjectSet.java
index 50a5381..33c2b57 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/DomainObjectSet.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/DomainObjectSet.java
@@ -25,17 +25,17 @@ import java.util.Set;
  */
 public interface DomainObjectSet<T> extends Set<T> {
     /**
-     * Returns the elements of this set as a list, in iteration order of this set.
+     * Returns the elements of this set in the set's iteration order.
      *
-     * @return The elements.
+     * @return The elements of this set in the set's iteration order.
      */
     List<T> getAll();
 
     /**
-     * Returns the element of this set at the given index in iteration order of this set.
+     * Returns the element at the given index according to the set's iteration order.
      *
      * @param index The index of the element to get.
-     * @return The element.
+     * @return The element at the given index according to the set's iteration order.
      */
     T getAt(int index) throws IndexOutOfBoundsException;
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java
index 40bc19c..9529eaf 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Element.java
@@ -16,24 +16,27 @@
 
 package org.gradle.tooling.model;
 
+import org.gradle.api.Nullable;
+
 /**
- * Described model element
+ * Described model element.
  *
  * @since 1.0-milestone-5
  */
 public interface Element extends Model {
 
     /**
-     * Returns the name. Note that the name is not a unique identifier.
+     * Returns the name of the element. Note that the name is not a unique identifier.
      *
-     * @return The name.
+     * @return The name of the element.
      */
     String getName();
 
     /**
-     * Returns the description.
+     * Returns the description of the element, or {@code null} if it has no description.
      *
-     * @return The description. May be null.
+     * @return The description of the element, or {@code null} if it has no description.
      */
+    @Nullable
     String getDescription();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ExternalDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ExternalDependency.java
index 4783ae1..7e68178 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ExternalDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/ExternalDependency.java
@@ -15,6 +15,9 @@
  */
 package org.gradle.tooling.model;
 
+import org.gradle.api.Experimental;
+import org.gradle.api.Nullable;
+
 import java.io.File;
 
 /**
@@ -24,21 +27,35 @@ public interface ExternalDependency extends Dependency {
     /**
      * Returns the file for this dependency.
      *
-     * @return The file. Never null.
+     * @return The file for this dependency.
      */
     File getFile();
 
     /**
-     * Returns the source directory/archive for this dependency.
+     * Returns the source directory or archive for this dependency, or {@code null} if no source is available.
      *
-     * @return The source file. Returns null when the source is not available for this dependency.
+     * @return The source directory or archive for this dependency, or {@code null} if no source is available.
      */
+    @Nullable
     File getSource();
 
     /**
-     * Returns the Javadoc directory/archive for this dependency.
+     * Returns the Javadoc directory or archive for this dependency, or {@code null} if no Javadoc is available.
      *
-     * @return The Javadoc file. Returns null when the Javadoc is not available for this dependency.
+     * @return the Javadoc directory or archive for this dependency, or {@code null} if no Javadoc is available.
      */
+    @Nullable
     File getJavadoc();
+
+    /**
+     * Returns the Gradle module information for this dependency, or {@code null} if the dependency does not
+     * originate from a remote repository.
+     *
+     * @return The Gradle module information for this dependency, or {@code null} if the dependency does not
+     * originate from a remote repository.
+     * @since 1.1-rc-1
+     */
+    @Nullable
+    @Experimental
+    GradleModuleVersion getGradleModuleVersion();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleModuleVersion.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleModuleVersion.java
new file mode 100644
index 0000000..1c39d9d
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleModuleVersion.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.model;
+
+import org.gradle.api.Experimental;
+
+/**
+ * Informs about a module version, i.e. group, name, version.
+ *
+ * @since 1.1-rc-1
+ */
+ at Experimental
+public interface GradleModuleVersion {
+
+    /**
+     * The group of the module, for example 'org.gradle'.
+     */
+    String getGroup();
+
+    /**
+     * The name of the module, for example 'gradle-tooling-api'.
+     */
+    String getName();
+
+    /**
+     * The version, for example '1.0'.
+     */
+    String getVersion();
+
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java
index def0938..98b9968 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleProject.java
@@ -16,6 +16,8 @@
 
 package org.gradle.tooling.model;
 
+import org.gradle.api.Nullable;
+
 /**
  * Gradle project.
  *
@@ -41,16 +43,17 @@ public interface GradleProject extends HierarchicalElement, BuildableElement {
     DomainObjectSet<? extends GradleProject> getChildren();
 
     /**
-     * Returns gradle path
+     * Returns Gradle path.
      *
      * @return The path.
      */
     String getPath();
 
     /**
-     * searches all descendants (children, grand children, etc.), including self, by given path.
+     * Searches all descendants (children, grand children, etc.), including self, by given path.
      *
-     * @return gradle project with matching path or null if not found
+     * @return Gradle project with matching path or {@code null} if not found.
      */
+    @Nullable
     GradleProject findByPath(String path);
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java
index d003832..241483c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/GradleTask.java
@@ -24,7 +24,7 @@ package org.gradle.tooling.model;
 public interface GradleTask extends Task {
 
     /**
-     * Returns the gradle project this task is defined in.
+     * Returns the Gradle project this task is defined in.
      *
      * @return The element.
      */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HasGradleProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HasGradleProject.java
index f271e9b..f9a2448 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HasGradleProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HasGradleProject.java
@@ -17,16 +17,12 @@
 package org.gradle.tooling.model;
 
 /**
- * Is associated with a Gradle project.
- * <p>
- * Via the gradle project you can access (list, run, etc.) gradle tasks
+ * An element that is associated with a Gradle project. Via the Gradle project you can access (list, run, etc.) Gradle tasks.
  */
 public interface HasGradleProject {
 
     /**
-     * The associated gradle project.
-     * <p>
-     * Via the gradle project you can access (list, run, etc.) gradle tasks
+     * The associated Gradle project. Via the gradle project you can access (list, run, etc.) Gradle tasks.
      */
     GradleProject getGradleProject();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java
index 5f75423..d858c88 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/HierarchicalElement.java
@@ -16,6 +16,8 @@
 
 package org.gradle.tooling.model;
 
+import org.gradle.api.Nullable;
+
 /**
  * Represents an element which belongs to some hierarchy.
  *
@@ -24,16 +26,17 @@ package org.gradle.tooling.model;
 public interface HierarchicalElement extends Element {
 
     /**
-     * Returns the parent of this element, if any.
+     * Returns the parent of this element, or {@code null} if there is no parent.
      *
-     * @return The parent, or null if it has no parent.
+     * @return The parent of this element, or {@code null} if there is no parent.
      */
+    @Nullable
     HierarchicalElement getParent();
 
     /**
-     * Returns the child elements.
+     * Returns the child elements, or the empty set if there are no child elements.
      *
-     * @return The child elements. Returns an empty set if it has no children.
+     * @return The child elements, or the empty set if there are no child elements.
      */
     DomainObjectSet<? extends HierarchicalElement> getChildren();
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java
index 58644df..d01bf9d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Model.java
@@ -17,9 +17,8 @@
 package org.gradle.tooling.model;
 
 /**
- * A Model that is buildable by the Tooling API.
- * Models contain various information regarding the build.
- * Models are typically tailored to specific domain (for example build environment or IDE, etc.)
+ * A model that is buildable by the Tooling API. Models contain various information regarding the build.
+ * Models are typically tailored to a specific domain, for example build environment or IDE.
  *
  * @since 1.0-milestone-8
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/SourceDirectory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/SourceDirectory.java
index e523d8e..49be091 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/SourceDirectory.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/SourceDirectory.java
@@ -24,7 +24,7 @@ public interface SourceDirectory {
     /**
      * Returns the source directory.
      *
-     * @return The directory. Does not return null.
+     * @return The source directory.
      */
     File getDirectory();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Task.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Task.java
index 256b3ca..2c91289 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Task.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/Task.java
@@ -15,33 +15,38 @@
  */
 package org.gradle.tooling.model;
 
+import org.gradle.api.Nullable;
+
 /**
  * Represents a task which is executable by Gradle.
  */
 public interface Task {
     /**
      * Returns the path of this task. This is a fully qualified unique name for this task.
+     *
+     * @return The path of this task.
      */
     String getPath();
 
     /**
      * Returns the name of this task. Note that the name is not necessarily a unique identifier for the task.
      *
-     * @return The name.
+     * @return The name of this task.
      */
     String getName();
 
     /**
-     * Returns the description of this task.
+     * Returns the description of this task, or {@code null} if it has no description.
      *
-     * @return The description. May be null.
+     * @return The description of this task, or {@code null} if it has no description.
      */
+    @Nullable
     String getDescription();
 
     /**
      * Returns the element which this task belongs to.
      *
-     * @return The element.
+     * @return The element which this task belongs to.
      */
     Element getProject();
     //TODO SF rename to 'owner'? I'd deprecate the Task interface and leave only GradleTask (or push this method down to the GradleTask)
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/UnsupportedMethodException.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/UnsupportedMethodException.java
index 84fd2cc..7a8707d 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/UnsupportedMethodException.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/UnsupportedMethodException.java
@@ -17,11 +17,11 @@
 package org.gradle.tooling.model;
 
 /**
- * Thrown when the tooling api client client attempts to use a method that does not exist
- * in the version of gradle the tooling api is connected to.
+ * Thrown when the tooling API client attempts to use a method that does not exist
+ * in the version of Gradle that the tooling API is connected to.
  * <p>
- * Typically, to resolve such problem you change/upgrade the target version of Gradle the tooling api is connected to.
- * Alternatively, you can handle and ignore this exception.
+ * Typically, to resolve such a problem you change/upgrade the target version of Gradle that
+ * the tooling API is connected to. Alternatively, you can handle and ignore this exception.
  *
  * @since 1.0-milestone-8
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java
index f61f2bd..7319247 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/BuildEnvironment.java
@@ -20,7 +20,7 @@ import org.gradle.tooling.model.Model;
 import org.gradle.tooling.model.UnsupportedMethodException;
 
 /**
- * Informs about the build environment, like Gradle version or the java home in use.
+ * Informs about the build environment, like Gradle version or the Java home in use.
  * <p>
  * Example:
  * <pre autoTested=''>
@@ -42,15 +42,15 @@ import org.gradle.tooling.model.UnsupportedMethodException;
 public interface BuildEnvironment extends Model {
 
     /**
-     * Informs about the gradle environment, for example the gradle version.
+     * Informs about the Gradle environment, for example the Gradle version.
      */
     GradleEnvironment getGradle();
 
     /**
-     * Informs about the java environment, for example the java home or the jvm args used.
+     * Informs about the Java environment, for example the Java home or the JVM args used.
      *
      * @throws org.gradle.tooling.model.UnsupportedMethodException
-     * when the gradle version the tooling api is connected to does not support the java environment information.
+     * when the Gradle version the tooling API is connected to does not support the Java environment information.
      */
     JavaEnvironment getJava() throws UnsupportedMethodException;
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java
index 71763b8..f4fe35c 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/GradleEnvironment.java
@@ -17,7 +17,7 @@
 package org.gradle.tooling.model.build;
 
 /**
- * Informs about the gradle environment, for example the gradle version.
+ * Informs about the Gradle environment, for example the Gradle version.
  * <p>
  * See example in {@link BuildEnvironment}
  *
@@ -26,7 +26,7 @@ package org.gradle.tooling.model.build;
 public interface GradleEnvironment {
 
     /**
-     * Informs about the gradle version.
+     * Informs about the Gradle version.
      */
     String getGradleVersion();
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java
index b8d6bb7..6571b69 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/JavaEnvironment.java
@@ -20,26 +20,23 @@ import java.io.File;
 import java.util.List;
 
 /**
- * Informs about the java environment, for example the java home or the jvm args used.
- * <p>
- * See example in {@link BuildEnvironment}
+ * Informs about the Java environment, for example the Java home or the JVM args used.
+ * See example in {@link BuildEnvironment}.
  *
  * @since 1.0-milestone-8
  */
 public interface JavaEnvironment {
 
     /**
-     * The java home used for gradle operations (e.g. running tasks or acquiring model information, etc).
+     * The Java home used for Gradle operations (for example running tasks or acquiring model information).
      */
     File getJavaHome();
 
     /**
-     * The jvm arguments used to start the java process that handles gradle operations
-     * (e.g. running tasks or acquiring model information, etc).
-     * <p>
-     * The returned jvm arguments that were used to start the java process.
-     * They do not include system properties passed as -Dfoo=bar.
-     * They may include the implicitly immutable system properties like "file.encoding".
+     * The JVM arguments used to start the Java process that handles Gradle operations
+     * (for example running tasks or acquiring model information).
+     * The returned arguments do not include system properties passed as -Dfoo=bar.
+     * They may include implicitly immutable system properties like "file.encoding".
      */
     List<String> getJvmArguments();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java
index 431bfe2..1b23dc3 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/build/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Models the build environment information like gradle or java versions
+ * Models the build environment information like Gradle or Java versions.
  */
 package org.gradle.tooling.model.build;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java
index 5d9e308..578ac5b 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/BasicIdeaProject.java
@@ -20,7 +20,7 @@ package org.gradle.tooling.model.idea;
  * IdeaProject that does not provide/resolve any external dependencies.
  * Only project dependencies and local file dependencies are included on the modules' classpath.
  * <p>
- * Useful for 'previewing' the output model of IdeaProject because it supposed to be fast (e.g. does not download dependencies from the web).
+ * Useful for 'previewing' the output model of IdeaProject because it is supposed to be fast (e.g. does not download dependencies from the web).
  */
 public interface BasicIdeaProject extends IdeaProject {
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaCompilerOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaCompilerOutput.java
index 44cc9b2..04899ba 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaCompilerOutput.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaCompilerOutput.java
@@ -16,10 +16,12 @@
 
 package org.gradle.tooling.model.idea;
 
+import org.gradle.api.Nullable;
+
 import java.io.File;
 
 /**
- * Idea compiler ouput settings
+ * IDEA compiler output settings.
  */
 public interface IdeaCompilerOutput {
 
@@ -38,6 +40,7 @@ public interface IdeaCompilerOutput {
      * @return directory to store production output. non-<code>null</code> if
      *            {@link #getInheritOutputDirs()} returns <code>'false'</code>
      */
+    @Nullable
     File getOutputDir();
 
     /**
@@ -46,5 +49,6 @@ public interface IdeaCompilerOutput {
      * @return directory to store test output. non-<code>null</code> if
      *            {@link #getInheritOutputDirs()} returns <code>'false'</code>
      */
+    @Nullable
     File getTestOutputDir();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaContentRoot.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaContentRoot.java
index 5246288..1f55559 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaContentRoot.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaContentRoot.java
@@ -22,7 +22,7 @@ import java.io.File;
 import java.util.Set;
 
 /**
- * Contains content root information
+ * Contains content root information.
  */
 public interface IdeaContentRoot {
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java
index 0fba0d7..8e8df96 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependency.java
@@ -19,7 +19,7 @@ package org.gradle.tooling.model.idea;
 import org.gradle.tooling.model.Dependency;
 
 /**
- * Idea dependency
+ * IDEA dependency.
  *
  * @since 1.0-milestone-5
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependencyScope.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependencyScope.java
index 8f68fc4..fdcdf60 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependencyScope.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaDependencyScope.java
@@ -17,7 +17,7 @@
 package org.gradle.tooling.model.idea;
 
 /**
- * The scope of the Idea dependency
+ * The scope of the IDEA dependency.
  */
 public interface IdeaDependencyScope {
 
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaLanguageLevel.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaLanguageLevel.java
index 5cf1987..4a7b798 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaLanguageLevel.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaLanguageLevel.java
@@ -17,7 +17,7 @@
 package org.gradle.tooling.model.idea;
 
 /**
- * Language level setting for IDEA
+ * Language level setting for IDEA.
  *
  * @since 1.0-milestone-5
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java
index 18a54a3..b354858 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModule.java
@@ -22,7 +22,7 @@ import org.gradle.tooling.model.HasGradleProject;
 import org.gradle.tooling.model.HierarchicalElement;
 
 /**
- * Represents information about the IntelliJ IDEA module
+ * Represents information about the IDEA module.
  *
  * @since 1.0-milestone-5
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModuleDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModuleDependency.java
index f4bd57f..6a60c5e 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModuleDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaModuleDependency.java
@@ -17,7 +17,7 @@
 package org.gradle.tooling.model.idea;
 
 /**
- * dependency to a module in a project
+ * Dependency on a module in a project.
  *
  * @since 1.0-milestone-5
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java
index 658dd12..5f43e3a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaProject.java
@@ -21,39 +21,39 @@ import org.gradle.tooling.model.Element;
 import org.gradle.tooling.model.HierarchicalElement;
 
 /**
- * Represents the information about the IntelliJ IDEA project
+ * Represents the information about the IDEA project.
  *
  * @since 1.0-milestone-5
  */
 public interface IdeaProject extends HierarchicalElement, Element {
 
     /**
-     * The name of the jdk
+     * Returns the name of the JDK.
      *
-     * @return jdk name
+     * @return The name of the JDK.
      */
     String getJdkName();
 
     /**
-     * Language level to use within the current project.
+     * Returns the language level to use within the current project.
      *
-     * @return language level
+     * @return The language level to use within the current project.
      */
     IdeaLanguageLevel getLanguageLevel();
 
     /**
-     * Returns modules of this idea project. Most projects have at least one module.
-     * Alias to {@link #getModules()}
+     * Returns the modules of this IDEA project. Most projects have at least one module.
+     * Alias for {@link #getModules()}.
      *
-     * @return modules
+     * @return The modules of this IDEA project.
      */
     DomainObjectSet<? extends IdeaModule> getChildren();
 
     /**
-     * Returns modules of this idea project. Most projects have at least one module.
-     * Alias to {@link #getChildren()}
+     * Returns the modules of this IDEA project. Most projects have at least one module.
+     * Alias for {@link #getChildren()}.
      *
-     * @return modules
+     * @return The modules of this IDEA project.
      */
     DomainObjectSet<? extends IdeaModule> getModules();
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSingleEntryLibraryDependency.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSingleEntryLibraryDependency.java
index 61c0791..27a0b8a 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSingleEntryLibraryDependency.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSingleEntryLibraryDependency.java
@@ -16,12 +16,13 @@
 
 package org.gradle.tooling.model.idea;
 
+import org.gradle.api.Nullable;
 import org.gradle.tooling.model.ExternalDependency;
 
 import java.io.File;
 
 /**
- * "Single-Entry Module Library" as IDEA calls it. For example a single jar file with sources jar.
+ * "Single-Entry Module Library" as IDEA calls it. For example a single Jar file with sources Jar.
  *
  * @since 1.0-milestone-5
  */
@@ -38,6 +39,7 @@ public interface IdeaSingleEntryLibraryDependency extends IdeaDependency, Extern
      *
      * @return The source file. Returns null when the source is not available for this dependency.
      */
+    @Nullable
     File getSource();
 
     /**
@@ -45,5 +47,6 @@ public interface IdeaSingleEntryLibraryDependency extends IdeaDependency, Extern
      *
      * @return The Javadoc file. Returns null when the Javadoc is not available for this dependency.
      */
+    @Nullable
     File getJavadoc();
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSourceDirectory.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSourceDirectory.java
index e5ef911..ea862de 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSourceDirectory.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/IdeaSourceDirectory.java
@@ -19,7 +19,7 @@ package org.gradle.tooling.model.idea;
 import org.gradle.tooling.model.SourceDirectory;
 
 /**
- * Idea source directory
+ * IDEA source directory.
  *
  * @since 1.0-milestone-5
  */
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java
index 01dc985..42ca84b 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/idea/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * IntelliJ IDEA related API of the tooling API
+ * IntelliJ IDEA related API of the tooling API.
  */
 package org.gradle.tooling.model.idea;
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/ImmutableDomainObjectSet.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/ImmutableDomainObjectSet.java
index 644cf89..f8f53ee 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/ImmutableDomainObjectSet.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/ImmutableDomainObjectSet.java
@@ -16,15 +16,17 @@
 package org.gradle.tooling.model.internal;
 
 import org.gradle.tooling.model.DomainObjectSet;
-import org.gradle.util.GUtil;
 
+import java.io.Serializable;
 import java.util.*;
 
-public class ImmutableDomainObjectSet<T> extends AbstractSet<T> implements DomainObjectSet<T> {
+public class ImmutableDomainObjectSet<T> extends AbstractSet<T> implements DomainObjectSet<T>, Serializable {
     private final Set<T> elements = new LinkedHashSet<T>();
 
     public ImmutableDomainObjectSet(Iterable<? extends T> elements) {
-        GUtil.addToCollection(this.elements, elements);
+        for (T element : elements) {
+            this.elements.add(element);
+        }
     }
 
     @Override
@@ -44,4 +46,8 @@ public class ImmutableDomainObjectSet<T> extends AbstractSet<T> implements Domai
     public List<T> getAll() {
         return new ArrayList<T>(elements);
     }
+
+    public static <T> ImmutableDomainObjectSet<T> of(Iterable<? extends T> elements) {
+        return new ImmutableDomainObjectSet<T>(elements);
+    }
 }
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/Archive.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/Archive.java
new file mode 100644
index 0000000..c383a10
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/Archive.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.model.internal.migration;
+
+import java.io.File;
+
+/**
+ * An archive produced by the build.
+ */
+public interface Archive extends TaskOutput {
+    File getFile();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/ProjectOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/ProjectOutput.java
new file mode 100644
index 0000000..1bb9fdb
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/ProjectOutput.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.model.internal.migration;
+
+import org.gradle.tooling.model.DomainObjectSet;
+import org.gradle.tooling.model.HierarchicalElement;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * The outputs produced by a Gradle project.
+ */
+public interface ProjectOutput extends HierarchicalElement {
+    ProjectOutput getParent();
+    DomainObjectSet<ProjectOutput> getChildren();
+    String getPath();
+    File getProjectDirectory();
+    Set<TaskOutput> getTaskOutputs();
+}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TaskOutput.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TaskOutput.java
new file mode 100644
index 0000000..b250abf
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TaskOutput.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.model.internal.migration;
+
+import org.gradle.tooling.model.Model;
+
+/**
+ * An output produced by a Gradle task.
+ */
+public interface TaskOutput extends Model {}
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TestResult.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TestResult.java
new file mode 100644
index 0000000..94b2a3e
--- /dev/null
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/internal/migration/TestResult.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 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 org.gradle.tooling.model.internal.migration;
+
+import java.io.File;
+
+/**
+ * The results from a test run.
+ */
+public interface TestResult extends TaskOutput {
+    File getXmlReportDir();
+}
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
index 4e7fa7a..184dcc4 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/DistributionFactoryTest.groovy
@@ -71,7 +71,7 @@ class DistributionFactoryTest extends Specification {
 
         expect:
         def dist = factory.getDistribution(tmpDir.dir)
-        dist.getToolingImplementationClasspath(progressLoggerFactory) == [libA, libB] as Set
+        dist.getToolingImplementationClasspath(progressLoggerFactory).asFiles as Set == [libA, libB] as Set
     }
 
     def failsWhenInstallationDirectoryDoesNotExist() {
@@ -127,7 +127,7 @@ class DistributionFactoryTest extends Specification {
         def dist = factory.getDistribution(zipFile.toURI())
 
         expect:
-        dist.getToolingImplementationClasspath(progressLoggerFactory).collect { it.name } as Set == ['a.jar', 'b.jar'] as Set
+        dist.getToolingImplementationClasspath(progressLoggerFactory).asFiles.name as Set == ['a.jar', 'b.jar'] as Set
     }
 
     def reportsZipDownload() {
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
index b9b1cd0..8a74c85 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/SynchronizedLoggingTest.groovy
@@ -20,6 +20,8 @@ package org.gradle.tooling.internal.consumer;
 import org.gradle.tests.fixtures.ConcurrentTestUtil
 import spock.lang.Specification
 
+import java.util.concurrent.CopyOnWriteArraySet
+
 /**
  * by Szczepan Faber, created at: 12/16/11
  */
@@ -49,7 +51,7 @@ public class SynchronizedLoggingTest extends Specification {
     def "keeps state per thread"() {
         given:
 
-        Set loggingTools = []
+        Set loggingTools = new CopyOnWriteArraySet()
 
         when:
         2.times {
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoaderTest.groovy
index 6f777ff..fad0b4a 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoaderTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/CachingToolingImplementationLoaderTest.groovy
@@ -19,6 +19,7 @@ import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.tooling.internal.consumer.Distribution
 import org.gradle.tooling.internal.consumer.connection.ConsumerConnection
 import spock.lang.Specification
+import org.gradle.internal.classpath.DefaultClassPath
 
 class CachingToolingImplementationLoaderTest extends Specification {
     final ToolingImplementationLoader target = Mock()
@@ -35,7 +36,7 @@ class CachingToolingImplementationLoaderTest extends Specification {
         then:
         impl == connection
         1 * target.create(distribution, loggerFactory, true) >> connection
-        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> ([new File('a.jar')] as Set)
+        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(new File('a.jar'))
         0 * _._
     }
 
@@ -51,7 +52,7 @@ class CachingToolingImplementationLoaderTest extends Specification {
         impl == connection
         impl2 == connection
         1 * target.create(distribution, loggerFactory, true) >> connection
-        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> ([new File('a.jar')] as Set)
+        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> { new DefaultClassPath(new File('a.jar')) }
         0 * _._
     }
 
@@ -70,8 +71,8 @@ class CachingToolingImplementationLoaderTest extends Specification {
         impl2 == connection2
         1 * target.create(distribution1, loggerFactory, true) >> connection1
         1 * target.create(distribution2, loggerFactory, false) >> connection2
-        _ * distribution1.getToolingImplementationClasspath(loggerFactory) >> ([new File('a.jar')] as Set)
-        _ * distribution2.getToolingImplementationClasspath(loggerFactory) >> ([new File('b.jar')] as Set)
+        _ * distribution1.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(new File('a.jar'))
+        _ * distribution2.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(new File('b.jar'))
         0 * _._
     }
 }
diff --git a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy
index 3a0216a..2023a59 100644
--- a/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy
+++ b/subprojects/tooling-api/src/test/groovy/org/gradle/tooling/internal/consumer/loader/DefaultToolingImplementationLoaderTest.groovy
@@ -25,6 +25,7 @@ import org.gradle.util.TemporaryFolder
 import org.junit.Rule
 import org.slf4j.Logger
 import spock.lang.Specification
+import org.gradle.internal.classpath.DefaultClassPath
 
 class DefaultToolingImplementationLoaderTest extends Specification {
     @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
@@ -34,14 +35,13 @@ class DefaultToolingImplementationLoaderTest extends Specification {
     def usesMetaInfServiceToDetermineFactoryImplementation() {
         given:
         def loader = new DefaultToolingImplementationLoader()
-        distribution.getToolingImplementationClasspath(loggerFactory) >> ([
+        distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath(
                 getToolingApiResourcesDir(),
                 ClasspathUtil.getClasspathForClass(TestConnection.class),
                 ClasspathUtil.getClasspathForClass(ActorFactory.class),
                 ClasspathUtil.getClasspathForClass(Logger.class),
                 getVersionResourcesDir(),
-                ClasspathUtil.getClasspathForClass(GradleVersion.class)
-        ] as Set)
+                ClasspathUtil.getClasspathForClass(GradleVersion.class))
 
         when:
         def adaptedConnection = loader.create(distribution, loggerFactory, true)
@@ -57,7 +57,7 @@ class DefaultToolingImplementationLoaderTest extends Specification {
     }
 
     private getVersionResourcesDir() {
-        return ClasspathUtil.getClasspathForResource(getClass().classLoader, "org/gradle/releases.xml")
+        return ClasspathUtil.getClasspathForResource(getClass().classLoader, "org/gradle/build-receipt.properties")
     }
 
     def failsWhenNoImplementationDeclared() {
@@ -70,7 +70,7 @@ class DefaultToolingImplementationLoaderTest extends Specification {
         then:
         UnsupportedVersionException e = thrown()
         e.message == "The specified <dist-display-name> is not supported by this tooling API version (${GradleVersion.current().version}, protocol version 4)"
-        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> ([] as Set)
+        _ * distribution.getToolingImplementationClasspath(loggerFactory) >> new DefaultClassPath()
         _ * distribution.displayName >> '<dist-display-name>'
     }
 }
\ No newline at end of file
diff --git a/subprojects/tooling-api/tooling-api.gradle b/subprojects/tooling-api/tooling-api.gradle
index c30aef5..f4e5db5 100644
--- a/subprojects/tooling-api/tooling-api.gradle
+++ b/subprojects/tooling-api/tooling-api.gradle
@@ -1,11 +1,12 @@
-import org.gradle.internal.os.OperatingSystem
 
 dependencies {
     groovy libraries.groovy
 
     publishCompile project(':core')
     publishCompile project(':baseServices')
+    publishCompile project(':messaging')
     publishCompile project(':wrapper')
+    publishCompile libraries.slf4j_api
 
     // lots of integTest errors otherwise
     integTestRuntime project(':ide')
@@ -13,13 +14,10 @@ dependencies {
 
 useTestFixtures()
 
-// is some of this unnecessary, or should it be moved into gradle/integTest ?
-integTest {
+integTestTasks.all {
     dependsOn ':publishLocalArchives', ':binZip'
 
     doFirst {
-        systemProperties['integTest.distsDir'] = rootProject.distsDir.absolutePath
-        systemProperties['integTest.libsRepo'] = rootProject.file('build/repo')
         systemProperties['org.gradle.integtest.toolingApiFromTestClasspath'] = 'true'
     }
 }
@@ -27,5 +25,3 @@ integTest {
 daemonIntegTest {
     enabled = false //tooling integ tests use daemon anyway, don't rerun
 }
-
-
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
index 582d0ae..3828384 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.integtests;
 
-import junit.framework.AssertionFailedError;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.foundation.BuildInformation;
@@ -139,7 +138,7 @@ public class FavoritesIntegrationTest {
             }
 
             public void reportError(String error) {
-                throw new AssertionFailedError("Unexpected error");
+                throw new AssertionError("Unexpected error");
             }
         });
 
@@ -184,7 +183,7 @@ public class FavoritesIntegrationTest {
         //Make sure the correct file doesn't already exist before we've even done our test. This is highly unlikely, but it might happen.
         //Technically, I should place these in a new temporary directory, but I didn't want the hassle of cleanup.
         if (correctFile.exists()) {
-            throw new AssertionFailedError("'correct' file already exists. This means this test WILL succeed but perhaps not for the correct reasons.");
+            throw new AssertionError("'correct' file already exists. This means this test WILL succeed but perhaps not for the correct reasons.");
         }
 
         //do the export
@@ -192,7 +191,7 @@ public class FavoritesIntegrationTest {
 
         //it should have been saved to the correct file
         if (!correctFile.exists()) {
-            throw new AssertionFailedError("failed to correct the file name. Expected it to be saved to '" + correctFile.getAbsolutePath() + "'");
+            throw new AssertionError("failed to correct the file name. Expected it to be saved to '" + correctFile.getAbsolutePath() + "'");
         }
 
         //now read in the file to verify it actually worked.
@@ -427,7 +426,7 @@ public class FavoritesIntegrationTest {
         }
 
         public void reportError(String error) {
-            throw new AssertionFailedError("Unexpected error; " + error);
+            throw new AssertionError("Unexpected error; " + error);
         }
     }
 
@@ -456,7 +455,7 @@ public class FavoritesIntegrationTest {
             }
 
             public void reportError(String error) {
-                throw new AssertionFailedError("Unexpected error");
+                throw new AssertionError("Unexpected error");
             }
         });
 
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java
index 0f48f71..e1ae29c 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java
@@ -15,7 +15,6 @@
  */
 package org.gradle.foundation;
 
-import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
@@ -272,7 +271,7 @@ public class FavoritesTest extends TestCase {
             }
 
             public void reportError(String error) {
-                throw new AssertionFailedError("unexpected error: " + error);
+                throw new AssertionError("unexpected error: " + error);
             }
         });
 
@@ -324,7 +323,7 @@ public class FavoritesTest extends TestCase {
             }
 
             public void reportError(String error) {
-                throw new AssertionFailedError("unexpected error: " + error);
+                throw new AssertionError("unexpected error: " + error);
             }
         });
 
@@ -353,7 +352,7 @@ public class FavoritesTest extends TestCase {
             }
 
             public void reportError(String error) {
-                throw new AssertionFailedError("unexpected error: " + error);
+                throw new AssertionError("unexpected error: " + error);
             }
         });
 
@@ -400,7 +399,7 @@ public class FavoritesTest extends TestCase {
             }
 
             public void reportError(String error) {
-                throw new AssertionFailedError("unexpected error: " + error);
+                throw new AssertionError("unexpected error: " + error);
             }
         });
 
@@ -706,7 +705,7 @@ public class FavoritesTest extends TestCase {
         }
 
         public void favoritesChanged() {
-            throw new AssertionFailedError("Did not expect to get a favoritesChanged notification!");
+            throw new AssertionError("Did not expect to get a favoritesChanged notification!");
         }
 
         public void favoritesReordered(List<FavoriteTask> favoritesReordered) {
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java
index 1643c89..56a3c53 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java
@@ -16,7 +16,6 @@
 package org.gradle.foundation;
 
 import junit.framework.TestCase;
-import junit.framework.AssertionFailedError;
 import org.gradle.foundation.output.FileLink;
 import org.gradle.foundation.output.FileLinkDefinitionLord;
 import org.gradle.foundation.output.LiveOutputParser;
@@ -70,7 +69,7 @@ public class LiveOutputParserTests extends TestCase {
     private void appendTextWithoutFileLinks(String text) {
         List<FileLink> fileLinks = parser.appendText(text);
         if (!fileLinks.isEmpty()) {
-            throw new AssertionFailedError("FileLinks list is erroneously not empty: " + TestUtility.dumpList(fileLinks));
+            throw new AssertionError("FileLinks list is erroneously not empty: " + TestUtility.dumpList(fileLinks));
         }
     }
 
diff --git a/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java b/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
index cebd902..c0390ac 100644
--- a/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
+++ b/subprojects/ui/src/test/groovy/org/gradle/foundation/TestUtility.java
@@ -16,7 +16,6 @@
 package org.gradle.foundation;
 
 import junit.framework.Assert;
-import junit.framework.AssertionFailedError;
 import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.tasks.TaskContainer;
@@ -204,12 +203,12 @@ public class TestUtility {
             T expectedObject = expectedObjecsList.remove(0);
 
             if (!actualObjecs.contains(expectedObject)) {
-                throw new AssertionFailedError("Failed to locate object. Sought object:\n" + expectedObject + "\n\nExpected:\n" + dumpList(expectedObjects) + "\nActual:\n" + dumpList(actualObjecs));
+                throw new AssertionError("Failed to locate object. Sought object:\n" + expectedObject + "\n\nExpected:\n" + dumpList(expectedObjects) + "\nActual:\n" + dumpList(actualObjecs));
             }
         }
 
         if (actualObjecs.size() != expectedObjects.size()) {
-            throw new AssertionFailedError("Expected " + expectedObjects.size() + " items but found " + actualObjecs.size() + "\nExpected:\n" + dumpList(expectedObjects) + "\nActual:\n" + dumpList(
+            throw new AssertionError("Expected " + expectedObjects.size() + " items but found " + actualObjecs.size() + "\nExpected:\n" + dumpList(expectedObjects) + "\nActual:\n" + dumpList(
                     actualObjecs));
         }
     }
@@ -255,7 +254,7 @@ public class TestUtility {
 
         public File promptForFile(FileFilter fileFilters) {
             if (promptCount == 100) {
-                throw new AssertionFailedError("Possible endless loop. PromptForFile has been called 100 times.");
+                throw new AssertionError("Possible endless loop. PromptForFile has been called 100 times.");
             }
 
             promptCount++;
@@ -273,7 +272,7 @@ public class TestUtility {
         }
 
         public void reportError(String error) {
-            throw new AssertionFailedError("Unexpected error: " + error);
+            throw new AssertionError("Unexpected error: " + error);
         }
     }
 
@@ -290,7 +289,7 @@ public class TestUtility {
 
         public File promptForFile(FileFilter fileFilters) {
             if (promptCount == 100) {
-                throw new AssertionFailedError("Possible endless loop. PromptForFile has been called 100 times.");
+                throw new AssertionError("Possible endless loop. PromptForFile has been called 100 times.");
             }
 
             promptCount++;
@@ -298,7 +297,7 @@ public class TestUtility {
         }
 
         public void reportError(String error) {
-            throw new AssertionFailedError("Unexpected error: " + error);
+            throw new AssertionError("Unexpected error: " + error);
         }
     }
 
@@ -376,10 +375,10 @@ public class TestUtility {
         if (!completed) {
             //its still running. Something is wrong.
             request.cancel(); //just to clean up after ourselves a little, cancel the request.
-            throw new AssertionFailedError("Failed to complete refresh in alotted time: " + maximumWaitValue + " " + maximumWaitUnits + ". Considering this failed.");
+            throw new AssertionError("Failed to complete refresh in alotted time: " + maximumWaitValue + " " + maximumWaitUnits + ". Considering this failed.");
         }
         if (errorOutput.get() != null) {
-            throw new AssertionFailedError(String.format("Command failed with output:%n%s", errorOutput.get()));
+            throw new AssertionError(String.format("Command failed with output:%n%s", errorOutput.get()));
         }
     }
 
@@ -434,7 +433,7 @@ public class TestUtility {
         if (timeout) {
             //its still running. Something is wrong.
             request.cancel(); //just to clean up after ourselves a little, cancel the request.
-            throw new AssertionFailedError("Failed to comlete execution in alotted time: " + maximumWaitSeconds + " seconds. Considering this failed.");
+            throw new AssertionError("Failed to comlete execution in alotted time: " + maximumWaitSeconds + " seconds. Considering this failed.");
         }
     }
 }
diff --git a/subprojects/website/website.gradle b/subprojects/website/website.gradle
deleted file mode 100644
index 1ae1be9..0000000
--- a/subprojects/website/website.gradle
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright 2011 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.
- */
-import java.util.regex.Pattern
-
-buildscript {
-    dependencies.classpath 'net.java.dev.jets3t:jets3t:0.8.1'
-    repositories.mavenCentral()
-}
-
-apply plugin: "base"
-evaluationDependsOn ":docs"
-
-// fail early if we don't have credentials to connect to the host
-gradle.taskGraph.whenReady { graph ->
-    tasks.withType(S3PutTask).matching { graph.hasTask(it) }.all {
-        accessKey project.gradleS3AccessKey
-        secretKey project.gradleS3SecretKey
-    }
-}
-
-task uploadDistributions(type: S3DistributionFileUpload) {
-    bucketName "downloads.gradle.org"
-    source rootProject.testedDists
-    directory "distributions${-> project.version.release ? "" : "-snapshots"}"
-}
-
-task generateReleasesXml() {
-    ext.outputFile = file("$buildDir/releases.xml")
-    outputs.file outputFile
-    outputs.upToDateWhen { false }
-
-    doLast {
-        project.releases.modifyTo(outputFile) {
-            def releases = release
-
-            current[0] + {
-                def readVersion = { label ->
-                    try {
-                        def text = new URL("http://gradle.org/versions/$label").text
-                        new groovy.json.JsonSlurper().parseText(text)
-                    } catch (FileNotFoundException e) {
-                        // service returns 404 if there is no version with that label,
-                        // so we get a FileNotFoundException from URL.text
-                        null
-                    }
-                }
-
-                // nightly
-                def nightlyVersion
-                def nightlyBuildTime
-                if (this.project.isNightlyBuild()) {
-                    nightlyVersion = this.project.version
-                    nightlyBuildTime = this.project.version.timestamp
-                } else {
-                    def nightlyRemote = readVersion("nightly")
-                    if (nightlyRemote) {
-                        nightlyVersion = nightlyRemote.version
-                        nightlyBuildTime = nightlyRemote.buildTime
-                    }
-                }
-
-                if (nightlyVersion && nightlyBuildTime) {
-                    release(version: nightlyVersion, "build-time": nightlyBuildTime, nightly: true, snapshot: true)
-                }
-
-                // rc
-                if (!this.project.isFinalReleaseBuild()) { // wipe out the rc if we are in a final release
-                    if (this.project.isRcBuild()) {
-                        def nextNode = next[0]
-                        assert nextNode
-                        release(version: this.project.version, "build-time": this.project.version.timestamp, "rc-for": nextNode. at version, snapshot: true)
-                    } else {
-                        def rcRemote = readVersion("release-candidate")
-                        if (rcRemote) {
-                            release(version: rcRemote.version, "build-time": rcRemote.buildTime, "rc-for": rcRemote.rcFor, snapshot: true)
-                        }
-                    }
-                }
-
-                // current
-                def currentVersion
-                def currentBuildTime
-                if (this.project.version.release) {
-                    
-                    /*
-                        We are doing a release build.
-                        We are relying on the project.releases.incrementNextVersion() NOT being called yet.
-                    */
-                    currentVersion = this.project.version
-                    currentBuildTime = this.project.version.timestamp
-                    release(version: currentVersion, "build-time": currentBuildTime, current: true)
-                } else {
-                    def currentRemote = readVersion("current")
-                    currentVersion = currentRemote.version
-
-                    def currentRelease = releases.find { it. at version == currentVersion }
-                    assert currentRelease : "didn't find $currentVersion in source releases.xml"
-                    currentRelease. at current = true
-                }
-            }
-
-            [next, current]*.each { remove(it) }
-        }
-    }
-}
-
-task checkoutRepo(type: Exec) {
-    ext.checkoutDir = file("$buildDir/repo-master")
-    onlyIf { !checkoutDir.exists() }
-    executable "git"
-    args "clone", "git at github.com:gradleware/web.git", checkoutDir
-}
-
-task pushReleasesXml {
-    dependsOn checkoutRepo, generateReleasesXml
-    outputs.upToDateWhen { false }
-    ext.repo = project.file("$project.buildDir/repo-$name")
-
-    doLast {
-        def masterCheckout = project.checkoutRepo.checkoutDir
-        def gitOnMasterCheckout = { Object[] cliArgs ->
-            project.exec {
-                workingDir masterCheckout
-                executable "git"
-                args cliArgs
-            }
-        }
-
-        gitOnMasterCheckout "reset", "--hard", "HEAD"
-        gitOnMasterCheckout "clean", "-f", "-d"
-        gitOnMasterCheckout "pull"
-
-        project.delete repo
-
-        // Gradle copy chokes on the symlinks
-        ant.copy(todir: repo) {
-            fileset(dir: masterCheckout, defaultexcludes: false)
-        }
-
-        def releasesXml = new File(repo, "data/releases.xml")
-        def checkedInReleasesXmlText = releasesXml.text
-        def newReleasesXmlText = generateReleasesXml.outputFile.text
-        if (checkedInReleasesXmlText != newReleasesXmlText) {
-            releasesXml.text = newReleasesXmlText
-
-            def gitOnTaskRepo = { Object[] cliArgs ->
-                project.exec {
-                    workingDir repo
-                    executable "git"
-                    args cliArgs
-                }
-            }
-
-            def message = "updating releases.xml from "
-            if (isFinalReleaseBuild()) {
-                message += "final release build"
-            } else if (isNightlyBuild()) {
-                message += "nightly build"
-            } else if (isRcBuild()) {
-                message += "release-candidate build"
-            } else {
-                message += "adhoc build"
-            }
-
-            gitOnTaskRepo "add", releasesXml.absolutePath
-            gitOnTaskRepo "commit", "-m", "[gradle-build] $message"
-
-            if (!project.hasProperty("noPushReleasesXml")) {
-                gitOnTaskRepo "push"
-            }
-        } else {
-            println "Not pushing new releases.xml to site as there were no changes after generation"
-        }
-    }
-
-}
-
-
-task docsWithAnalytics(type: PatternTransform) {
-    inputs.files project(":docs").docsZip
-    from { zipTree(project(":docs").docsZip.outputs.files.singleFile) }
-    into "$buildDir/$name"
-    transform ".*(?<!(javadoc|groovydoc)/index)\\.html", Pattern.compile("<\\s*/\\s*head\\s*>", Pattern.CASE_INSENSITIVE), """
-            <script type="text/javascript">
-              var _gaq = _gaq || [];
-              _gaq.push(['_setAccount', 'UA-4207603-1']);
-              _gaq.push(['_trackPageview']);
-
-              (function() {
-                var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
-                ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
-                var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
-              })();
-            </script>
-            <script type="text/javascript" language="javascript">llactid=20600</script>
-            <script type="text/javascript" language="javascript" src="http://t3.trackalyzer.com/trackalyze.js"></script>
-        </head>
-    """.trim()
-}
-
-task docsWithAnalyticsZip(type: Zip) {
-    from docsWithAnalytics
-    baseName "online-docs"
-}
-
-task uploadDocs(type: S3DistributionFileUpload) {
-    bucketName "downloads.gradle.org"
-    source docsWithAnalyticsZip
-    directory "online-docs${-> project.version.release ? "" : "-snapshots"}"
-}
-
-task pullDocs {
-    doLast {
-        new URL("http://gradle.org/update-docs/").text
-    }
-}
-
-class PatternTransform extends Sync {
-
-    private transformCounter = 0
-
-    void transform(relativePathPattern, toReplacePattern, replaceWith) {
-        relativePathPattern = compilePattern(relativePathPattern)
-        toReplacePattern = compilePattern(toReplacePattern)
-
-        inputs.property "transform:${transformCounter++}", [
-            relativePathPattern: asMap(relativePathPattern),
-            toReplacePattern: asMap(toReplacePattern),
-            replaceWith: replaceWith
-        ]
-
-        eachFile {
-            if (relativePathPattern.matcher(it.relativePath.toString()).matches()) {
-                it.filter {
-                    toReplacePattern.matcher(it).replaceAll(replaceWith)
-                }
-            }
-        }
-    }
-
-    private asMap(Pattern pattern) {
-        [pattern: pattern.toString(), flags: pattern.flags]
-    }
-
-    protected Pattern compilePattern(pattern) {
-        pattern instanceof Pattern ? pattern : Pattern.compile(pattern)
-    }
-}
-
-import org.jets3t.service.security.AWSCredentials
-import org.jets3t.service.security.ProviderCredentials
-import org.jets3t.service.model.S3Object
-import org.jets3t.service.S3Service
-import org.jets3t.service.impl.rest.httpclient.RestS3Service
-import org.gradle.api.Action
-import org.gradle.listener.ActionBroadcast
-// only needed for doLast block
-import org.jets3t.service.model.StorageObject
-
-class S3DistributionFileUpload extends S3PutTask {
-    @Input directory
-    
-    S3DistributionFileUpload() {
-        bucketName "downloads.gradle.org"
-        eachObject {
-            if (it.key.endsWith(".zip")) {
-                it.contentType = "application/zip"
-            } else {
-                throw new InvalidUserDataException("Don't know content type for file: $it.key")
-            }
-            it.key = "$directory/$name"
-        }
-
-        // S3 incorrectly treats “+” incorrectly as a “ ”
-        // https://forums.aws.amazon.com/thread.jspa?threadID=55746
-        // We compensate by also providing files with spaces (for unencoded “+”) and files with “+” (for encoded “+”)
-        doLast {
-            def service = createService()
-            source.each {
-                def key = "$directory/$it.name"
-                if (key.contains("+")) {
-                    def copyDestination = key.replace("+", " ")
-                    logger.lifecycle "making copy of '$key' to '$copyDestination'"
-                    service.copyObject(bucketName, key, bucketName, new StorageObject(copyDestination), false)
-                }
-            }
-        }
-    }
-}
-
-class S3PutTask extends SourceTask {
-    @Input accessKey
-    @Input secretKey
-    @Input bucketName
-    @Input @Optional friendlyName
-
-    protected ActionBroadcast<S3Object> eachObjects = new ActionBroadcast<S3Object>()
-
-    void eachObject(Action<S3Object> action) {
-        eachObjects.add(action)
-    }
-
-    @TaskAction
-    void put() {
-        def service = createService()
-        source.each { File file ->
-            def s3Object = new S3Object(file)
-            s3Object.addMetadata("gradle-release-date", project.version.timestamp)
-            eachObjects.execute(s3Object)
-            logger.lifecycle "uploading '$file.name' to '$s3Object.key'"
-            service.putObject(bucketName, s3Object)
-        }
-    }
-
-    ProviderCredentials createCredentials() {
-        new AWSCredentials(accessKey, secretKey, friendlyName)
-    }
-
-    S3Service createService() {
-        new RestS3Service(createCredentials())
-    }
-}
\ No newline at end of file

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



More information about the pkg-java-commits mailing list